Overview
ETH Balance
0 ETH
Eth Value
$0.00More Info
Private Name Tags
ContractCreator
Loading...
Loading
Contract Name:
DataProvider
Compiler Version
v0.8.20+commit.a1b79de6
Optimization Enabled:
Yes with 1000 runs
Other Settings:
paris EvmVersion
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/utils/math/Math.sol"; import "@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol"; import "@openzeppelin/contracts/utils/math/SafeCast.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "../library/ExternalContractAddresses.sol"; import "../stUSC.sol"; import "../interfaces/IPriceFeedAggregator.sol"; import "../interfaces/IStakingWithEpochs.sol"; import "../interfaces/IOCHI.sol"; import "../staking/RewardControllerV2.sol"; import "../staking/ChiStaking.sol"; import "../staking/ChiLocking.sol"; import "../staking/LPStaking.sol"; import "../staking/ChiVesting.sol"; import "../dso/PoolHelper.sol"; import "../dso/OCHI.sol"; import "../interfaces/IOCHI.sol"; import "../staking/USCStaking.sol"; import "../staking/StakingManager.sol"; import "../staking/LockingManager.sol"; import "../deprecated/ReserveHolder.sol"; import "../dso/LPRewards.sol"; /// @title Data provider /// @notice Data provider containing view functions used by frontend contract DataProvider { using SafeCast for uint256; uint256 public constant DAYS_IN_YEAR = 365; uint256 public constant SECOND_IN_YEAR = 31536000; struct StEthReward { uint256 tokenValue; uint256 usdValue; } struct ChiReward { uint256 tokenValue; uint256 usdValue; } struct UscReward { uint256 tokenValue; uint256 usdValue; } struct Reward { StEthReward stEthReward; ChiReward chiReward; UscReward uscReward; uint256 totalReward; } struct Rewards { Reward stUscRewards; Reward stChiRewards; Reward veChiRewards; Reward uscEthLpRewards; Reward chiEthLpRewards; uint256 totalStEthReward; uint256 totalStEthRewardUsd; uint256 totalChiReward; uint256 totalChiRewardUsd; } function uscStakingApr(stUSC _stUSC) public view returns (uint256) { return (_stUSC.emissionPerSecond() * SECOND_IN_YEAR) / _stUSC.totalSupply(); } function lpStakingApr( address chi, StakingManager lpStaking, IPriceFeedAggregator priceFeedAggregator, IUniswapV2Pair uscEthLpToken, IUniswapV2Pair chiEthLpToken ) public view returns ( uint256 stEthAprUscEthLp, uint256 weEthAprUscEthLp, uint256 chiAprUscEthLp, uint256 totalUscEthLpApr, uint256 stEthAprChiEthLp, uint256 weEthAprChiEthLp, uint256 chiAprChiEthLp, uint256 totalChiEthLpApr ) { uint256 stEthRewardsPerSecond = (lpStaking.getRewardTokenConfig(address(uscEthLpToken), ExternalContractAddresses.stETH)).emissionPerSecond; uint256 weEthRewardsPerSecond = lpStaking.getRewardTokenConfig(address(uscEthLpToken), ExternalContractAddresses.weETH).emissionPerSecond; uint256 chiRewardsPerSecond = lpStaking.getRewardTokenConfig(address(uscEthLpToken), chi).emissionPerSecond; uint256 stEthPrice = priceFeedAggregator.peek(ExternalContractAddresses.stETH); uint256 weEthPrice = priceFeedAggregator.peek(ExternalContractAddresses.weETH); uint256 chiPrice = priceFeedAggregator.peek(chi); uint256 uscEthLpTokenPrice = getLPTokenPrice(uscEthLpToken, priceFeedAggregator); uint256 chiEthLpTokenPrice = getLPTokenPrice(chiEthLpToken, priceFeedAggregator); stEthAprUscEthLp = ((stEthRewardsPerSecond * SECOND_IN_YEAR * stEthPrice) / (lpStaking.getTotalStaked(address(uscEthLpToken)) * uscEthLpTokenPrice)) * 100; weEthAprUscEthLp = ((weEthRewardsPerSecond * SECOND_IN_YEAR * weEthPrice) / (lpStaking.getTotalStaked(address(uscEthLpToken)) * uscEthLpTokenPrice)) * 100; chiAprUscEthLp = ((chiRewardsPerSecond * SECOND_IN_YEAR * chiPrice) / (lpStaking.getTotalStaked(address(uscEthLpToken)) * uscEthLpTokenPrice)) * 100; totalUscEthLpApr = stEthAprUscEthLp + weEthAprUscEthLp + chiAprUscEthLp; stEthRewardsPerSecond = lpStaking.getRewardTokenConfig(address(chiEthLpToken), ExternalContractAddresses.stETH).emissionPerSecond; weEthRewardsPerSecond = lpStaking.getRewardTokenConfig(address(chiEthLpToken), ExternalContractAddresses.weETH).emissionPerSecond; chiRewardsPerSecond = lpStaking.getRewardTokenConfig(address(chiEthLpToken), chi).emissionPerSecond; stEthAprChiEthLp = ((stEthRewardsPerSecond * SECOND_IN_YEAR * stEthPrice) / (lpStaking.getTotalStaked(address(chiEthLpToken)) * chiEthLpTokenPrice)) * 100; weEthAprChiEthLp = ((weEthRewardsPerSecond * SECOND_IN_YEAR * weEthPrice) / (lpStaking.getTotalStaked(address(chiEthLpToken)) * chiEthLpTokenPrice)) * 100; chiAprChiEthLp = ((chiRewardsPerSecond * SECOND_IN_YEAR * chiPrice) / (lpStaking.getTotalStaked(address(chiEthLpToken)) * chiEthLpTokenPrice)) * 100; totalChiEthLpApr = stEthAprChiEthLp + weEthAprChiEthLp + chiAprChiEthLp; return (stEthAprUscEthLp, weEthAprUscEthLp, chiAprUscEthLp, totalUscEthLpApr, stEthAprChiEthLp, weEthAprChiEthLp, chiAprChiEthLp, totalChiEthLpApr); } // dodaj funkcije za lp staking koje su vec uradjene function chiLockingAPR( address chi, ChiStaking chiStaking, ChiLocking chiLocking, USCStaking uscStaking, LPStaking uscEthLpStaking, LPStaking chiEthLpStaking, ChiVesting chiVesting, RewardControllerV2 rewardController, IPriceFeedAggregator priceFeedAggregator, ReserveHolder reserveHolder ) public view returns (uint256 totalApr, uint256 chiApr, uint256 stChiApr) { uint256 chiPrice = priceFeedAggregator.peek(chi); uint256 totalLockedChiValue = Math.mulDiv(chiLocking.getLockedChi() + chiVesting.getLockedChi(), chiPrice, 1e8); uint256 chiEmissions = rewardController.chiIncentivesForChiLocking(); uint256 chiEmissionsValue = Math.mulDiv(chiEmissions, chiPrice, 1e8); chiApr = Math.mulDiv(chiEmissionsValue * 52, 1e18, totalLockedChiValue); (, stChiApr, , , ) = chiStakingAPR( chi, chiStaking, chiLocking, uscStaking, uscEthLpStaking, chiEthLpStaking, chiVesting, rewardController, priceFeedAggregator, reserveHolder ); return (chiApr + stChiApr, chiApr, stChiApr); } function chiStakingAPR( address chi, ChiStaking chiStaking, ChiLocking chiLocking, USCStaking uscStaking, LPStaking uscEthLpStaking, LPStaking chiEthLpStaking, ChiVesting chiVesting, RewardControllerV2 rewardController, IPriceFeedAggregator priceFeedAggregator, ReserveHolder reserveHolder ) public view returns ( uint256 chiStakingAprInStEth, uint256 chiLockingAprInStEth, uint256 uscStakingAprInStEth, uint256 uscEthLpStakingAprInStEth, uint256 chiEthLpStakingAprInStEth ) { uint256 stEthPrice = priceFeedAggregator.peek(ExternalContractAddresses.stETH); uint256 currentEpoch = chiStaking.currentEpoch(); (, uint256 totalRewardsTwoEpochsAgo) = currentEpoch >= 2 ? rewardController.epochs(currentEpoch - 2) : (0, 0); (, uint256 totalRewardsLastEpoch) = rewardController.epochs(currentEpoch - 1); uint256 totalEthReward; if (currentEpoch < 4) { totalEthReward = (reserveHolder.totalStEthDeposited() * 4) / 100 / 52; } else { totalEthReward = totalRewardsLastEpoch - totalRewardsTwoEpochsAgo; } uint256 chiPrice = priceFeedAggregator.peek(chi); uint256 totalEthRewardValue = Math.mulDiv(totalEthReward, stEthPrice, 1e8); uint256 stEthForChiStaking = Math.mulDiv( totalEthRewardValue, rewardController.stEthPercentageForChiStaking(), 10000 ); uint256 chiStakedValue = Math.mulDiv(chiStaking.getStakedChi(), chiPrice, 1e8); chiStakingAprInStEth = Math.mulDiv(stEthForChiStaking * 52, 1e18, chiStakedValue); uint256 stEthForChiLocking = Math.mulDiv( totalEthRewardValue, rewardController.stEthPercentageForChiLocking(), 10000 ); uint256 chiLockedValue = Math.mulDiv(chiLocking.getStakedChi(), chiPrice, 1e8); chiLockingAprInStEth = Math.mulDiv(stEthForChiLocking * 52, 1e18, chiLockedValue); uint256 stEthForUscStaking = Math.mulDiv( totalEthRewardValue, rewardController.stEthPercentageForUscStaking(), 10000 ); uint256 uscStakedValue = uscStaking.totalSupply(); uscStakingAprInStEth = Math.mulDiv(stEthForUscStaking * 52, 1e18, uscStakedValue); uint256 uscEthLpTokenPrice = getLPTokenPrice( IUniswapV2Pair(address(uscEthLpStaking.stakeToken())), priceFeedAggregator ); uint256 stEthForUscEthLPStaking = Math.mulDiv( totalEthRewardValue, rewardController.stEthPercentageForUscEthLPStaking(), 10000 ); uint256 uscEthLPStakedValue = Math.mulDiv(uscEthLpStaking.totalSupply(), uscEthLpTokenPrice, 1e8); uscEthLpStakingAprInStEth = Math.mulDiv(stEthForUscEthLPStaking * 52, 1e18, uscEthLPStakedValue); uint256 chiEthLpTokenPrice = getLPTokenPrice( IUniswapV2Pair(address(chiEthLpStaking.stakeToken())), priceFeedAggregator ); uint256 stEthForChiEthLPStaking = Math.mulDiv( totalEthRewardValue, rewardController.stEthPercentageForChiEthLPStaking(), 10000 ); uint256 chiEthLPStakedValue = Math.mulDiv(chiEthLpStaking.totalSupply(), chiEthLpTokenPrice, 1e8); chiEthLpStakingAprInStEth = Math.mulDiv(stEthForChiEthLPStaking * 52, 1e18, chiEthLPStakedValue); return ( chiStakingAprInStEth, chiLockingAprInStEth, uscStakingAprInStEth, uscEthLpStakingAprInStEth, chiEthLpStakingAprInStEth ); } function getLPTokenPrice( IUniswapV2Pair pair, IPriceFeedAggregator priceFeedAggregator ) public view returns (uint256) { return PoolHelper.getUSDValueForLP(1 ether, pair, priceFeedAggregator); } function chiEthLpLockingApr( address chi, LockingManager lockingManager, StakingManager lpStaking, IPriceFeedAggregator priceFeedAggregator, IUniswapV2Pair uscEthLpToken, IUniswapV2Pair chiEthLpToken ) public view returns (uint256 baseApr, uint256 extraApr, uint256 totalApr) { ( , , , , , , ,uint256 totalChiEthLpApr) = lpStakingApr(chi, lpStaking, priceFeedAggregator, uscEthLpToken, chiEthLpToken); uint256 chiRewardsPerEpoch = lockingManager.rewardsPerEpoch(); uint256 chiPrice = priceFeedAggregator.peek(chi); uint256 totalLockedAmount = lockingManager.totalLockedAmount(); baseApr = totalChiEthLpApr; extraApr = chiRewardsPerEpoch * DAYS_IN_YEAR * chiPrice / totalLockedAmount * 100; return (baseApr, extraApr, baseApr + extraApr); } function uscEthLpLockingApr( address chi, LockingManager lockingManager, StakingManager lpStaking, IPriceFeedAggregator priceFeedAggregator, IUniswapV2Pair uscEthLpToken, IUniswapV2Pair chiEthLpToken ) public view returns (uint256 baseApr, uint256 extraApr, uint256 totalApr) { ( , , , uint256 totalUscEthLpApr, , , , ) = lpStakingApr(chi, lpStaking, priceFeedAggregator, uscEthLpToken, chiEthLpToken); uint256 chiRewardsPerEpoch = lockingManager.rewardsPerEpoch(); uint256 chiPrice = priceFeedAggregator.peek(chi); uint256 totalLockedAmount = lockingManager.totalLockedAmount(); baseApr = totalUscEthLpApr; extraApr = chiRewardsPerEpoch * DAYS_IN_YEAR * chiPrice / totalLockedAmount * 100; return (baseApr, extraApr, baseApr + extraApr); } function wstUscLockingApr( LockingManager lockingManager, address chi, stUSC _stUSC, IPriceFeedAggregator priceFeedAggregator ) public view returns (uint256 baseApr, uint256 extraApr, uint256 totalApr) { uint256 uscStakingApr = uscStakingApr(_stUSC); uint256 wstUSCLockingRewardsPerEpoch = lockingManager.rewardsPerEpoch(); uint256 chiPrice = priceFeedAggregator.peek(chi); uint256 totalLockedAmount = lockingManager.totalLockedAmount(); baseApr = uscStakingApr; extraApr = Math.mulDiv(wstUSCLockingRewardsPerEpoch * DAYS_IN_YEAR, chiPrice, totalLockedAmount) * 100; return (baseApr, extraApr, baseApr + extraApr); } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; interface AggregatorV3Interface { function decimals() external view returns (uint8); function description() external view returns (string memory); function version() external view returns (uint256); function getRoundData(uint80 _roundId) external view returns ( uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound ); function latestRoundData() external view returns ( uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound ); }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol) pragma solidity ^0.8.0; import "../utils/ContextUpgradeable.sol"; import {Initializable} from "../proxy/utils/Initializable.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 OwnableUpgradeable is Initializable, ContextUpgradeable { address private _owner; event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); /** * @dev Initializes the contract setting the deployer as the initial owner. */ function __Ownable_init() internal onlyInitializing { __Ownable_init_unchained(); } function __Ownable_init_unchained() internal onlyInitializing { _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); } /** * @dev This empty reserved space is put in place to allow future versions to add new * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ uint256[49] private __gap; }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC4626.sol) pragma solidity ^0.8.0; import "../token/ERC20/IERC20Upgradeable.sol"; import "../token/ERC20/extensions/IERC20MetadataUpgradeable.sol"; /** * @dev Interface of the ERC4626 "Tokenized Vault Standard", as defined in * https://eips.ethereum.org/EIPS/eip-4626[ERC-4626]. * * _Available since v4.7._ */ interface IERC4626Upgradeable is IERC20Upgradeable, IERC20MetadataUpgradeable { event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares); event Withdraw( address indexed sender, address indexed receiver, address indexed owner, uint256 assets, uint256 shares ); /** * @dev Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing. * * - MUST be an ERC-20 token contract. * - MUST NOT revert. */ function asset() external view returns (address assetTokenAddress); /** * @dev Returns the total amount of the underlying asset that is “managed” by Vault. * * - SHOULD include any compounding that occurs from yield. * - MUST be inclusive of any fees that are charged against assets in the Vault. * - MUST NOT revert. */ function totalAssets() external view returns (uint256 totalManagedAssets); /** * @dev Returns the amount of shares that the Vault would exchange for the amount of assets provided, in an ideal * scenario where all the conditions are met. * * - MUST NOT be inclusive of any fees that are charged against assets in the Vault. * - MUST NOT show any variations depending on the caller. * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange. * - MUST NOT revert. * * NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the * “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and * from. */ function convertToShares(uint256 assets) external view returns (uint256 shares); /** * @dev Returns the amount of assets that the Vault would exchange for the amount of shares provided, in an ideal * scenario where all the conditions are met. * * - MUST NOT be inclusive of any fees that are charged against assets in the Vault. * - MUST NOT show any variations depending on the caller. * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange. * - MUST NOT revert. * * NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the * “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and * from. */ function convertToAssets(uint256 shares) external view returns (uint256 assets); /** * @dev Returns the maximum amount of the underlying asset that can be deposited into the Vault for the receiver, * through a deposit call. * * - MUST return a limited value if receiver is subject to some deposit limit. * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of assets that may be deposited. * - MUST NOT revert. */ function maxDeposit(address receiver) external view returns (uint256 maxAssets); /** * @dev Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given * current on-chain conditions. * * - MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit * call in the same transaction. I.e. deposit should return the same or more shares as previewDeposit if called * in the same transaction. * - MUST NOT account for deposit limits like those returned from maxDeposit and should always act as though the * deposit would be accepted, regardless if the user has enough tokens approved, etc. * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees. * - MUST NOT revert. * * NOTE: any unfavorable discrepancy between convertToShares and previewDeposit SHOULD be considered slippage in * share price or some other type of condition, meaning the depositor will lose assets by depositing. */ function previewDeposit(uint256 assets) external view returns (uint256 shares); /** * @dev Mints shares Vault shares to receiver by depositing exactly amount of underlying tokens. * * - MUST emit the Deposit event. * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the * deposit execution, and are accounted for during deposit. * - MUST revert if all of assets cannot be deposited (due to deposit limit being reached, slippage, the user not * approving enough underlying tokens to the Vault contract, etc). * * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token. */ function deposit(uint256 assets, address receiver) external returns (uint256 shares); /** * @dev Returns the maximum amount of the Vault shares that can be minted for the receiver, through a mint call. * - MUST return a limited value if receiver is subject to some mint limit. * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of shares that may be minted. * - MUST NOT revert. */ function maxMint(address receiver) external view returns (uint256 maxShares); /** * @dev Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given * current on-chain conditions. * * - MUST return as close to and no fewer than the exact amount of assets that would be deposited in a mint call * in the same transaction. I.e. mint should return the same or fewer assets as previewMint if called in the * same transaction. * - MUST NOT account for mint limits like those returned from maxMint and should always act as though the mint * would be accepted, regardless if the user has enough tokens approved, etc. * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees. * - MUST NOT revert. * * NOTE: any unfavorable discrepancy between convertToAssets and previewMint SHOULD be considered slippage in * share price or some other type of condition, meaning the depositor will lose assets by minting. */ function previewMint(uint256 shares) external view returns (uint256 assets); /** * @dev Mints exactly shares Vault shares to receiver by depositing amount of underlying tokens. * * - MUST emit the Deposit event. * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the mint * execution, and are accounted for during mint. * - MUST revert if all of shares cannot be minted (due to deposit limit being reached, slippage, the user not * approving enough underlying tokens to the Vault contract, etc). * * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token. */ function mint(uint256 shares, address receiver) external returns (uint256 assets); /** * @dev Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the * Vault, through a withdraw call. * * - MUST return a limited value if owner is subject to some withdrawal limit or timelock. * - MUST NOT revert. */ function maxWithdraw(address owner) external view returns (uint256 maxAssets); /** * @dev Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block, * given current on-chain conditions. * * - MUST return as close to and no fewer than the exact amount of Vault shares that would be burned in a withdraw * call in the same transaction. I.e. withdraw should return the same or fewer shares as previewWithdraw if * called * in the same transaction. * - MUST NOT account for withdrawal limits like those returned from maxWithdraw and should always act as though * the withdrawal would be accepted, regardless if the user has enough shares, etc. * - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees. * - MUST NOT revert. * * NOTE: any unfavorable discrepancy between convertToShares and previewWithdraw SHOULD be considered slippage in * share price or some other type of condition, meaning the depositor will lose assets by depositing. */ function previewWithdraw(uint256 assets) external view returns (uint256 shares); /** * @dev Burns shares from owner and sends exactly assets of underlying tokens to receiver. * * - MUST emit the Withdraw event. * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the * withdraw execution, and are accounted for during withdraw. * - MUST revert if all of assets cannot be withdrawn (due to withdrawal limit being reached, slippage, the owner * not having enough shares, etc). * * Note that some implementations will require pre-requesting to the Vault before a withdrawal may be performed. * Those methods should be performed separately. */ function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares); /** * @dev Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault, * through a redeem call. * * - MUST return a limited value if owner is subject to some withdrawal limit or timelock. * - MUST return balanceOf(owner) if owner is not subject to any withdrawal limit or timelock. * - MUST NOT revert. */ function maxRedeem(address owner) external view returns (uint256 maxShares); /** * @dev Allows an on-chain or off-chain user to simulate the effects of their redeemption at the current block, * given current on-chain conditions. * * - MUST return as close to and no more than the exact amount of assets that would be withdrawn in a redeem call * in the same transaction. I.e. redeem should return the same or more assets as previewRedeem if called in the * same transaction. * - MUST NOT account for redemption limits like those returned from maxRedeem and should always act as though the * redemption would be accepted, regardless if the user has enough shares, etc. * - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees. * - MUST NOT revert. * * NOTE: any unfavorable discrepancy between convertToAssets and previewRedeem SHOULD be considered slippage in * share price or some other type of condition, meaning the depositor will lose assets by redeeming. */ function previewRedeem(uint256 shares) external view returns (uint256 assets); /** * @dev Burns exactly shares from owner and sends assets of underlying tokens to receiver. * * - MUST emit the Withdraw event. * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the * redeem execution, and are accounted for during redeem. * - MUST revert if all of shares cannot be redeemed (due to withdrawal limit being reached, slippage, the owner * not having enough shares, etc). * * NOTE: some implementations will require pre-requesting to the Vault before a withdrawal may be performed. * Those methods should be performed separately. */ function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets); }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC5267.sol) pragma solidity ^0.8.0; interface IERC5267Upgradeable { /** * @dev MAY be emitted to signal that the domain could have changed. */ event EIP712DomainChanged(); /** * @dev returns the fields and values that describe the domain separator used by this contract for EIP-712 * signature. */ function eip712Domain() external view returns ( bytes1 fields, string memory name, string memory version, uint256 chainId, address verifyingContract, bytes32 salt, uint256[] memory extensions ); }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (proxy/utils/Initializable.sol) pragma solidity ^0.8.2; import "../../utils/AddressUpgradeable.sol"; /** * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect. * * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be * reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in * case an upgrade adds a module that needs to be initialized. * * For example: * * [.hljs-theme-light.nopadding] * ```solidity * contract MyToken is ERC20Upgradeable { * function initialize() initializer public { * __ERC20_init("MyToken", "MTK"); * } * } * * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable { * function initializeV2() reinitializer(2) public { * __ERC20Permit_init("MyToken"); * } * } * ``` * * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}. * * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity. * * [CAUTION] * ==== * Avoid leaving a contract uninitialized. * * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed: * * [.hljs-theme-light.nopadding] * ``` * /// @custom:oz-upgrades-unsafe-allow constructor * constructor() { * _disableInitializers(); * } * ``` * ==== */ abstract contract Initializable { /** * @dev Indicates that the contract has been initialized. * @custom:oz-retyped-from bool */ uint8 private _initialized; /** * @dev Indicates that the contract is in the process of being initialized. */ bool private _initializing; /** * @dev Triggered when the contract has been initialized or reinitialized. */ event Initialized(uint8 version); /** * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope, * `onlyInitializing` functions can be used to initialize parent contracts. * * Similar to `reinitializer(1)`, except that functions marked with `initializer` can be nested in the context of a * constructor. * * Emits an {Initialized} event. */ modifier initializer() { bool isTopLevelCall = !_initializing; require( (isTopLevelCall && _initialized < 1) || (!AddressUpgradeable.isContract(address(this)) && _initialized == 1), "Initializable: contract is already initialized" ); _initialized = 1; if (isTopLevelCall) { _initializing = true; } _; if (isTopLevelCall) { _initializing = false; emit Initialized(1); } } /** * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be * used to initialize parent contracts. * * A reinitializer may be used after the original initialization step. This is essential to configure modules that * are added through upgrades and that require initialization. * * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer` * cannot be nested. If one is invoked in the context of another, execution will revert. * * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in * a contract, executing them in the right order is up to the developer or operator. * * WARNING: setting the version to 255 will prevent any future reinitialization. * * Emits an {Initialized} event. */ modifier reinitializer(uint8 version) { require(!_initializing && _initialized < version, "Initializable: contract is already initialized"); _initialized = version; _initializing = true; _; _initializing = false; emit Initialized(version); } /** * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the * {initializer} and {reinitializer} modifiers, directly or indirectly. */ modifier onlyInitializing() { require(_initializing, "Initializable: contract is not initializing"); _; } /** * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call. * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized * to any version. It is recommended to use this to lock implementation contracts that are designed to be called * through proxies. * * Emits an {Initialized} event the first time it is successfully executed. */ function _disableInitializers() internal virtual { require(!_initializing, "Initializable: contract is initializing"); if (_initialized != type(uint8).max) { _initialized = type(uint8).max; emit Initialized(type(uint8).max); } } /** * @dev Returns the highest version that has been initialized. See {reinitializer}. */ function _getInitializedVersion() internal view returns (uint8) { return _initialized; } /** * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}. */ function _isInitializing() internal view returns (bool) { return _initializing; } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/ERC20.sol) pragma solidity ^0.8.0; import "./IERC20Upgradeable.sol"; import "./extensions/IERC20MetadataUpgradeable.sol"; import "../../utils/ContextUpgradeable.sol"; import {Initializable} from "../../proxy/utils/Initializable.sol"; /** * @dev Implementation of the {IERC20} interface. * * This implementation is agnostic to the way tokens are created. This means * that a supply mechanism has to be added in a derived contract using {_mint}. * For a generic mechanism see {ERC20PresetMinterPauser}. * * TIP: For a detailed writeup see our guide * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How * to implement supply mechanisms]. * * The default value of {decimals} is 18. To change this, you should override * this function so it returns a different value. * * We have followed general OpenZeppelin Contracts guidelines: functions revert * instead returning `false` on failure. This behavior is nonetheless * conventional and does not conflict with the expectations of ERC20 * applications. * * Additionally, an {Approval} event is emitted on calls to {transferFrom}. * This allows applications to reconstruct the allowance for all accounts just * by listening to said events. Other implementations of the EIP may not emit * these events, as it isn't required by the specification. * * Finally, the non-standard {decreaseAllowance} and {increaseAllowance} * functions have been added to mitigate the well-known issues around setting * allowances. See {IERC20-approve}. */ contract ERC20Upgradeable is Initializable, ContextUpgradeable, IERC20Upgradeable, IERC20MetadataUpgradeable { mapping(address => uint256) private _balances; mapping(address => mapping(address => uint256)) private _allowances; uint256 private _totalSupply; string private _name; string private _symbol; /** * @dev Sets the values for {name} and {symbol}. * * All two of these values are immutable: they can only be set once during * construction. */ function __ERC20_init(string memory name_, string memory symbol_) internal onlyInitializing { __ERC20_init_unchained(name_, symbol_); } function __ERC20_init_unchained(string memory name_, string memory symbol_) internal onlyInitializing { _name = name_; _symbol = symbol_; } /** * @dev Returns the name of the token. */ function name() public view virtual override returns (string memory) { return _name; } /** * @dev Returns the symbol of the token, usually a shorter version of the * name. */ function symbol() public view virtual override returns (string memory) { return _symbol; } /** * @dev Returns the number of decimals used to get its user representation. * For example, if `decimals` equals `2`, a balance of `505` tokens should * be displayed to a user as `5.05` (`505 / 10 ** 2`). * * Tokens usually opt for a value of 18, imitating the relationship between * Ether and Wei. This is the default value returned by this function, unless * it's overridden. * * NOTE: This information is only used for _display_ purposes: it in * no way affects any of the arithmetic of the contract, including * {IERC20-balanceOf} and {IERC20-transfer}. */ function decimals() public view virtual override returns (uint8) { return 18; } /** * @dev See {IERC20-totalSupply}. */ function totalSupply() public view virtual override returns (uint256) { return _totalSupply; } /** * @dev See {IERC20-balanceOf}. */ function balanceOf(address account) public view virtual override returns (uint256) { return _balances[account]; } /** * @dev See {IERC20-transfer}. * * Requirements: * * - `to` cannot be the zero address. * - the caller must have a balance of at least `amount`. */ function transfer(address to, uint256 amount) public virtual override returns (bool) { address owner = _msgSender(); _transfer(owner, to, amount); return true; } /** * @dev See {IERC20-allowance}. */ function allowance(address owner, address spender) public view virtual override returns (uint256) { return _allowances[owner][spender]; } /** * @dev See {IERC20-approve}. * * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on * `transferFrom`. This is semantically equivalent to an infinite approval. * * Requirements: * * - `spender` cannot be the zero address. */ function approve(address spender, uint256 amount) public virtual override returns (bool) { address owner = _msgSender(); _approve(owner, spender, amount); return true; } /** * @dev See {IERC20-transferFrom}. * * Emits an {Approval} event indicating the updated allowance. This is not * required by the EIP. See the note at the beginning of {ERC20}. * * NOTE: Does not update the allowance if the current allowance * is the maximum `uint256`. * * Requirements: * * - `from` and `to` cannot be the zero address. * - `from` must have a balance of at least `amount`. * - the caller must have allowance for ``from``'s tokens of at least * `amount`. */ function transferFrom(address from, address to, uint256 amount) public virtual override returns (bool) { address spender = _msgSender(); _spendAllowance(from, spender, amount); _transfer(from, to, amount); return true; } /** * @dev Atomically increases the allowance granted to `spender` by the caller. * * This is an alternative to {approve} that can be used as a mitigation for * problems described in {IERC20-approve}. * * Emits an {Approval} event indicating the updated allowance. * * Requirements: * * - `spender` cannot be the zero address. */ function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) { address owner = _msgSender(); _approve(owner, spender, allowance(owner, spender) + addedValue); return true; } /** * @dev Atomically decreases the allowance granted to `spender` by the caller. * * This is an alternative to {approve} that can be used as a mitigation for * problems described in {IERC20-approve}. * * Emits an {Approval} event indicating the updated allowance. * * Requirements: * * - `spender` cannot be the zero address. * - `spender` must have allowance for the caller of at least * `subtractedValue`. */ function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) { address owner = _msgSender(); uint256 currentAllowance = allowance(owner, spender); require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero"); unchecked { _approve(owner, spender, currentAllowance - subtractedValue); } return true; } /** * @dev Moves `amount` of tokens from `from` to `to`. * * This internal function is equivalent to {transfer}, and can be used to * e.g. implement automatic token fees, slashing mechanisms, etc. * * Emits a {Transfer} event. * * Requirements: * * - `from` cannot be the zero address. * - `to` cannot be the zero address. * - `from` must have a balance of at least `amount`. */ function _transfer(address from, address to, uint256 amount) internal virtual { require(from != address(0), "ERC20: transfer from the zero address"); require(to != address(0), "ERC20: transfer to the zero address"); _beforeTokenTransfer(from, to, amount); uint256 fromBalance = _balances[from]; require(fromBalance >= amount, "ERC20: transfer amount exceeds balance"); unchecked { _balances[from] = fromBalance - amount; // Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by // decrementing then incrementing. _balances[to] += amount; } emit Transfer(from, to, amount); _afterTokenTransfer(from, to, amount); } /** @dev Creates `amount` tokens and assigns them to `account`, increasing * the total supply. * * Emits a {Transfer} event with `from` set to the zero address. * * Requirements: * * - `account` cannot be the zero address. */ function _mint(address account, uint256 amount) internal virtual { require(account != address(0), "ERC20: mint to the zero address"); _beforeTokenTransfer(address(0), account, amount); _totalSupply += amount; unchecked { // Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above. _balances[account] += amount; } emit Transfer(address(0), account, amount); _afterTokenTransfer(address(0), account, amount); } /** * @dev Destroys `amount` tokens from `account`, reducing the * total supply. * * Emits a {Transfer} event with `to` set to the zero address. * * Requirements: * * - `account` cannot be the zero address. * - `account` must have at least `amount` tokens. */ function _burn(address account, uint256 amount) internal virtual { require(account != address(0), "ERC20: burn from the zero address"); _beforeTokenTransfer(account, address(0), amount); uint256 accountBalance = _balances[account]; require(accountBalance >= amount, "ERC20: burn amount exceeds balance"); unchecked { _balances[account] = accountBalance - amount; // Overflow not possible: amount <= accountBalance <= totalSupply. _totalSupply -= amount; } emit Transfer(account, address(0), amount); _afterTokenTransfer(account, address(0), amount); } /** * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens. * * This internal function is equivalent to `approve`, and can be used to * e.g. set automatic allowances for certain subsystems, etc. * * Emits an {Approval} event. * * Requirements: * * - `owner` cannot be the zero address. * - `spender` cannot be the zero address. */ function _approve(address owner, address spender, uint256 amount) internal virtual { require(owner != address(0), "ERC20: approve from the zero address"); require(spender != address(0), "ERC20: approve to the zero address"); _allowances[owner][spender] = amount; emit Approval(owner, spender, amount); } /** * @dev Updates `owner` s allowance for `spender` based on spent `amount`. * * Does not update the allowance amount in case of infinite allowance. * Revert if not enough allowance is available. * * Might emit an {Approval} event. */ function _spendAllowance(address owner, address spender, uint256 amount) internal virtual { uint256 currentAllowance = allowance(owner, spender); if (currentAllowance != type(uint256).max) { require(currentAllowance >= amount, "ERC20: insufficient allowance"); unchecked { _approve(owner, spender, currentAllowance - amount); } } } /** * @dev Hook that is called before any transfer of tokens. This includes * minting and burning. * * Calling conditions: * * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens * will be transferred to `to`. * - when `from` is zero, `amount` tokens will be minted for `to`. * - when `to` is zero, `amount` of ``from``'s tokens will be burned. * - `from` and `to` are never both zero. * * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. */ function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {} /** * @dev Hook that is called after any transfer of tokens. This includes * minting and burning. * * Calling conditions: * * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens * has been transferred to `to`. * - when `from` is zero, `amount` tokens have been minted for `to`. * - when `to` is zero, `amount` of ``from``'s tokens have been burned. * - `from` and `to` are never both zero. * * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. */ function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual {} /** * @dev This empty reserved space is put in place to allow future versions to add new * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ uint256[45] private __gap; }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.4) (token/ERC20/extensions/ERC20Permit.sol) pragma solidity ^0.8.0; import "./IERC20PermitUpgradeable.sol"; import "../ERC20Upgradeable.sol"; import "../../../utils/cryptography/ECDSAUpgradeable.sol"; import "../../../utils/cryptography/EIP712Upgradeable.sol"; import "../../../utils/CountersUpgradeable.sol"; import {Initializable} from "../../../proxy/utils/Initializable.sol"; /** * @dev Implementation of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612]. * * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by * presenting a message signed by the account. By not relying on `{IERC20-approve}`, the token holder account doesn't * need to send a transaction, and thus is not required to hold Ether at all. * * _Available since v3.4._ * * @custom:storage-size 51 */ abstract contract ERC20PermitUpgradeable is Initializable, ERC20Upgradeable, IERC20PermitUpgradeable, EIP712Upgradeable { using CountersUpgradeable for CountersUpgradeable.Counter; mapping(address => CountersUpgradeable.Counter) private _nonces; // solhint-disable-next-line var-name-mixedcase bytes32 private constant _PERMIT_TYPEHASH = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); /** * @dev In previous versions `_PERMIT_TYPEHASH` was declared as `immutable`. * However, to ensure consistency with the upgradeable transpiler, we will continue * to reserve a slot. * @custom:oz-renamed-from _PERMIT_TYPEHASH */ // solhint-disable-next-line var-name-mixedcase bytes32 private _PERMIT_TYPEHASH_DEPRECATED_SLOT; /** * @dev Initializes the {EIP712} domain separator using the `name` parameter, and setting `version` to `"1"`. * * It's a good idea to use the same `name` that is defined as the ERC20 token name. */ function __ERC20Permit_init(string memory name) internal onlyInitializing { __EIP712_init_unchained(name, "1"); } function __ERC20Permit_init_unchained(string memory) internal onlyInitializing {} /** * @inheritdoc IERC20PermitUpgradeable */ function permit( address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s ) public virtual override { require(block.timestamp <= deadline, "ERC20Permit: expired deadline"); bytes32 structHash = keccak256(abi.encode(_PERMIT_TYPEHASH, owner, spender, value, _useNonce(owner), deadline)); bytes32 hash = _hashTypedDataV4(structHash); address signer = ECDSAUpgradeable.recover(hash, v, r, s); require(signer == owner, "ERC20Permit: invalid signature"); _approve(owner, spender, value); } /** * @inheritdoc IERC20PermitUpgradeable */ function nonces(address owner) public view virtual override returns (uint256) { return _nonces[owner].current(); } /** * @inheritdoc IERC20PermitUpgradeable */ // solhint-disable-next-line func-name-mixedcase function DOMAIN_SEPARATOR() external view override returns (bytes32) { return _domainSeparatorV4(); } /** * @dev "Consume a nonce": return the current value and increment. * * _Available since v4.1._ */ function _useNonce(address owner) internal virtual returns (uint256 current) { CountersUpgradeable.Counter storage nonce = _nonces[owner]; current = nonce.current(); nonce.increment(); } /** * @dev This empty reserved space is put in place to allow future versions to add new * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ uint256[49] private __gap; }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/ERC4626.sol) pragma solidity ^0.8.0; import "../ERC20Upgradeable.sol"; import "../utils/SafeERC20Upgradeable.sol"; import "../../../interfaces/IERC4626Upgradeable.sol"; import "../../../utils/math/MathUpgradeable.sol"; import {Initializable} from "../../../proxy/utils/Initializable.sol"; /** * @dev Implementation of the ERC4626 "Tokenized Vault Standard" as defined in * https://eips.ethereum.org/EIPS/eip-4626[EIP-4626]. * * This extension allows the minting and burning of "shares" (represented using the ERC20 inheritance) in exchange for * underlying "assets" through standardized {deposit}, {mint}, {redeem} and {burn} workflows. This contract extends * the ERC20 standard. Any additional extensions included along it would affect the "shares" token represented by this * contract and not the "assets" token which is an independent contract. * * [CAUTION] * ==== * In empty (or nearly empty) ERC-4626 vaults, deposits are at high risk of being stolen through frontrunning * with a "donation" to the vault that inflates the price of a share. This is variously known as a donation or inflation * attack and is essentially a problem of slippage. Vault deployers can protect against this attack by making an initial * deposit of a non-trivial amount of the asset, such that price manipulation becomes infeasible. Withdrawals may * similarly be affected by slippage. Users can protect against this attack as well as unexpected slippage in general by * verifying the amount received is as expected, using a wrapper that performs these checks such as * https://github.com/fei-protocol/ERC4626#erc4626router-and-base[ERC4626Router]. * * Since v4.9, this implementation uses virtual assets and shares to mitigate that risk. The `_decimalsOffset()` * corresponds to an offset in the decimal representation between the underlying asset's decimals and the vault * decimals. This offset also determines the rate of virtual shares to virtual assets in the vault, which itself * determines the initial exchange rate. While not fully preventing the attack, analysis shows that the default offset * (0) makes it non-profitable, as a result of the value being captured by the virtual shares (out of the attacker's * donation) matching the attacker's expected gains. With a larger offset, the attack becomes orders of magnitude more * expensive than it is profitable. More details about the underlying math can be found * xref:erc4626.adoc#inflation-attack[here]. * * The drawback of this approach is that the virtual shares do capture (a very small) part of the value being accrued * to the vault. Also, if the vault experiences losses, the users try to exit the vault, the virtual shares and assets * will cause the first user to exit to experience reduced losses in detriment to the last users that will experience * bigger losses. Developers willing to revert back to the pre-v4.9 behavior just need to override the * `_convertToShares` and `_convertToAssets` functions. * * To learn more, check out our xref:ROOT:erc4626.adoc[ERC-4626 guide]. * ==== * * _Available since v4.7._ */ abstract contract ERC4626Upgradeable is Initializable, ERC20Upgradeable, IERC4626Upgradeable { using MathUpgradeable for uint256; IERC20Upgradeable private _asset; uint8 private _underlyingDecimals; /** * @dev Set the underlying asset contract. This must be an ERC20-compatible contract (ERC20 or ERC777). */ function __ERC4626_init(IERC20Upgradeable asset_) internal onlyInitializing { __ERC4626_init_unchained(asset_); } function __ERC4626_init_unchained(IERC20Upgradeable asset_) internal onlyInitializing { (bool success, uint8 assetDecimals) = _tryGetAssetDecimals(asset_); _underlyingDecimals = success ? assetDecimals : 18; _asset = asset_; } /** * @dev Attempts to fetch the asset decimals. A return value of false indicates that the attempt failed in some way. */ function _tryGetAssetDecimals(IERC20Upgradeable asset_) private view returns (bool, uint8) { (bool success, bytes memory encodedDecimals) = address(asset_).staticcall( abi.encodeWithSelector(IERC20MetadataUpgradeable.decimals.selector) ); if (success && encodedDecimals.length >= 32) { uint256 returnedDecimals = abi.decode(encodedDecimals, (uint256)); if (returnedDecimals <= type(uint8).max) { return (true, uint8(returnedDecimals)); } } return (false, 0); } /** * @dev Decimals are computed by adding the decimal offset on top of the underlying asset's decimals. This * "original" value is cached during construction of the vault contract. If this read operation fails (e.g., the * asset has not been created yet), a default of 18 is used to represent the underlying asset's decimals. * * See {IERC20Metadata-decimals}. */ function decimals() public view virtual override(IERC20MetadataUpgradeable, ERC20Upgradeable) returns (uint8) { return _underlyingDecimals + _decimalsOffset(); } /** @dev See {IERC4626-asset}. */ function asset() public view virtual override returns (address) { return address(_asset); } /** @dev See {IERC4626-totalAssets}. */ function totalAssets() public view virtual override returns (uint256) { return _asset.balanceOf(address(this)); } /** @dev See {IERC4626-convertToShares}. */ function convertToShares(uint256 assets) public view virtual override returns (uint256) { return _convertToShares(assets, MathUpgradeable.Rounding.Down); } /** @dev See {IERC4626-convertToAssets}. */ function convertToAssets(uint256 shares) public view virtual override returns (uint256) { return _convertToAssets(shares, MathUpgradeable.Rounding.Down); } /** @dev See {IERC4626-maxDeposit}. */ function maxDeposit(address) public view virtual override returns (uint256) { return type(uint256).max; } /** @dev See {IERC4626-maxMint}. */ function maxMint(address) public view virtual override returns (uint256) { return type(uint256).max; } /** @dev See {IERC4626-maxWithdraw}. */ function maxWithdraw(address owner) public view virtual override returns (uint256) { return _convertToAssets(balanceOf(owner), MathUpgradeable.Rounding.Down); } /** @dev See {IERC4626-maxRedeem}. */ function maxRedeem(address owner) public view virtual override returns (uint256) { return balanceOf(owner); } /** @dev See {IERC4626-previewDeposit}. */ function previewDeposit(uint256 assets) public view virtual override returns (uint256) { return _convertToShares(assets, MathUpgradeable.Rounding.Down); } /** @dev See {IERC4626-previewMint}. */ function previewMint(uint256 shares) public view virtual override returns (uint256) { return _convertToAssets(shares, MathUpgradeable.Rounding.Up); } /** @dev See {IERC4626-previewWithdraw}. */ function previewWithdraw(uint256 assets) public view virtual override returns (uint256) { return _convertToShares(assets, MathUpgradeable.Rounding.Up); } /** @dev See {IERC4626-previewRedeem}. */ function previewRedeem(uint256 shares) public view virtual override returns (uint256) { return _convertToAssets(shares, MathUpgradeable.Rounding.Down); } /** @dev See {IERC4626-deposit}. */ function deposit(uint256 assets, address receiver) public virtual override returns (uint256) { require(assets <= maxDeposit(receiver), "ERC4626: deposit more than max"); uint256 shares = previewDeposit(assets); _deposit(_msgSender(), receiver, assets, shares); return shares; } /** @dev See {IERC4626-mint}. * * As opposed to {deposit}, minting is allowed even if the vault is in a state where the price of a share is zero. * In this case, the shares will be minted without requiring any assets to be deposited. */ function mint(uint256 shares, address receiver) public virtual override returns (uint256) { require(shares <= maxMint(receiver), "ERC4626: mint more than max"); uint256 assets = previewMint(shares); _deposit(_msgSender(), receiver, assets, shares); return assets; } /** @dev See {IERC4626-withdraw}. */ function withdraw(uint256 assets, address receiver, address owner) public virtual override returns (uint256) { require(assets <= maxWithdraw(owner), "ERC4626: withdraw more than max"); uint256 shares = previewWithdraw(assets); _withdraw(_msgSender(), receiver, owner, assets, shares); return shares; } /** @dev See {IERC4626-redeem}. */ function redeem(uint256 shares, address receiver, address owner) public virtual override returns (uint256) { require(shares <= maxRedeem(owner), "ERC4626: redeem more than max"); uint256 assets = previewRedeem(shares); _withdraw(_msgSender(), receiver, owner, assets, shares); return assets; } /** * @dev Internal conversion function (from assets to shares) with support for rounding direction. */ function _convertToShares(uint256 assets, MathUpgradeable.Rounding rounding) internal view virtual returns (uint256) { return assets.mulDiv(totalSupply() + 10 ** _decimalsOffset(), totalAssets() + 1, rounding); } /** * @dev Internal conversion function (from shares to assets) with support for rounding direction. */ function _convertToAssets(uint256 shares, MathUpgradeable.Rounding rounding) internal view virtual returns (uint256) { return shares.mulDiv(totalAssets() + 1, totalSupply() + 10 ** _decimalsOffset(), rounding); } /** * @dev Deposit/mint common workflow. */ function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal virtual { // If _asset is ERC777, `transferFrom` can trigger a reentrancy BEFORE the transfer happens through the // `tokensToSend` hook. On the other hand, the `tokenReceived` hook, that is triggered after the transfer, // calls the vault, which is assumed not malicious. // // Conclusion: we need to do the transfer before we mint so that any reentrancy would happen before the // assets are transferred and before the shares are minted, which is a valid state. // slither-disable-next-line reentrancy-no-eth SafeERC20Upgradeable.safeTransferFrom(_asset, caller, address(this), assets); _mint(receiver, shares); emit Deposit(caller, receiver, assets, shares); } /** * @dev Withdraw/redeem common workflow. */ function _withdraw( address caller, address receiver, address owner, uint256 assets, uint256 shares ) internal virtual { if (caller != owner) { _spendAllowance(owner, caller, shares); } // If _asset is ERC777, `transfer` can trigger a reentrancy AFTER the transfer happens through the // `tokensReceived` hook. On the other hand, the `tokensToSend` hook, that is triggered before the transfer, // calls the vault, which is assumed not malicious. // // Conclusion: we need to do the transfer after the burn so that any reentrancy would happen after the // shares are burned and after the assets are transferred, which is a valid state. _burn(owner, shares); SafeERC20Upgradeable.safeTransfer(_asset, receiver, assets); emit Withdraw(caller, receiver, owner, assets, shares); } function _decimalsOffset() internal view virtual returns (uint8) { return 0; } /** * @dev This empty reserved space is put in place to allow future versions to add new * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ uint256[49] private __gap; }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol) pragma solidity ^0.8.0; import "../IERC20Upgradeable.sol"; /** * @dev Interface for the optional metadata functions from the ERC20 standard. * * _Available since v4.1._ */ interface IERC20MetadataUpgradeable is IERC20Upgradeable { /** * @dev Returns the name of the token. */ function name() external view returns (string memory); /** * @dev Returns the symbol of the token. */ function symbol() external view returns (string memory); /** * @dev Returns the decimals places of the token. */ function decimals() external view returns (uint8); }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.4) (token/ERC20/extensions/IERC20Permit.sol) pragma solidity ^0.8.0; /** * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612]. * * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't * need to send a transaction, and thus is not required to hold Ether at all. * * ==== Security Considerations * * There are two important considerations concerning the use of `permit`. The first is that a valid permit signature * expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be * considered as an intention to spend the allowance in any specific way. The second is that because permits have * built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should * take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be * generally recommended is: * * ```solidity * function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public { * try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {} * doThing(..., value); * } * * function doThing(..., uint256 value) public { * token.safeTransferFrom(msg.sender, address(this), value); * ... * } * ``` * * Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of * `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also * {SafeERC20-safeTransferFrom}). * * Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so * contracts should have entry points that don't rely on permit. */ interface IERC20PermitUpgradeable { /** * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens, * given ``owner``'s signed approval. * * IMPORTANT: The same issues {IERC20-approve} has related to transaction * ordering also apply here. * * Emits an {Approval} event. * * Requirements: * * - `spender` cannot be the zero address. * - `deadline` must be a timestamp in the future. * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner` * over the EIP712-formatted function arguments. * - the signature must use ``owner``'s current nonce (see {nonces}). * * For more information on the signature format, see the * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP * section]. * * CAUTION: See Security Considerations above. */ function permit( address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s ) external; /** * @dev Returns the current nonce for `owner`. This value must be * included whenever a signature is generated for {permit}. * * Every successful call to {permit} increases ``owner``'s nonce by one. This * prevents a signature from being used multiple times. */ function nonces(address owner) external view returns (uint256); /** * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}. */ // solhint-disable-next-line func-name-mixedcase function DOMAIN_SEPARATOR() external view returns (bytes32); }
// 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 IERC20Upgradeable { /** * @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.3) (token/ERC20/utils/SafeERC20.sol) pragma solidity ^0.8.0; import "../IERC20Upgradeable.sol"; import "../extensions/IERC20PermitUpgradeable.sol"; import "../../../utils/AddressUpgradeable.sol"; /** * @title SafeERC20 * @dev Wrappers around ERC20 operations that throw on failure (when the token * contract returns false). Tokens that return no value (and instead revert or * throw on failure) are also supported, non-reverting calls are assumed to be * successful. * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract, * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. */ library SafeERC20Upgradeable { using AddressUpgradeable for address; /** * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value, * non-reverting calls are assumed to be successful. */ function safeTransfer(IERC20Upgradeable token, address to, uint256 value) internal { _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); } /** * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful. */ function safeTransferFrom(IERC20Upgradeable token, address from, address to, uint256 value) internal { _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); } /** * @dev Deprecated. This function has issues similar to the ones found in * {IERC20-approve}, and its usage is discouraged. * * Whenever possible, use {safeIncreaseAllowance} and * {safeDecreaseAllowance} instead. */ function safeApprove(IERC20Upgradeable token, address spender, uint256 value) internal { // safeApprove should only be called when setting an initial allowance, // or when resetting it to zero. To increase and decrease it, use // 'safeIncreaseAllowance' and 'safeDecreaseAllowance' require( (value == 0) || (token.allowance(address(this), spender) == 0), "SafeERC20: approve from non-zero to non-zero allowance" ); _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)); } /** * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value, * non-reverting calls are assumed to be successful. */ function safeIncreaseAllowance(IERC20Upgradeable token, address spender, uint256 value) internal { uint256 oldAllowance = token.allowance(address(this), spender); _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance + value)); } /** * @dev Decrease the calling contract's allowance toward `spender` by `value`. If `token` returns no value, * non-reverting calls are assumed to be successful. */ function safeDecreaseAllowance(IERC20Upgradeable token, address spender, uint256 value) internal { unchecked { uint256 oldAllowance = token.allowance(address(this), spender); require(oldAllowance >= value, "SafeERC20: decreased allowance below zero"); _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance - value)); } } /** * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value, * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval * to be set to zero before setting it to a non-zero value, such as USDT. */ function forceApprove(IERC20Upgradeable token, address spender, uint256 value) internal { bytes memory approvalCall = abi.encodeWithSelector(token.approve.selector, spender, value); if (!_callOptionalReturnBool(token, approvalCall)) { _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, 0)); _callOptionalReturn(token, approvalCall); } } /** * @dev Use a ERC-2612 signature to set the `owner` approval toward `spender` on `token`. * Revert on invalid signature. */ function safePermit( IERC20PermitUpgradeable token, address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s ) internal { uint256 nonceBefore = token.nonces(owner); token.permit(owner, spender, value, deadline, v, r, s); uint256 nonceAfter = token.nonces(owner); require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed"); } /** * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement * on the return value: the return value is optional (but if data is returned, it must not be false). * @param token The token targeted by the call. * @param data The call data (encoded using abi.encode or one of its variants). */ function _callOptionalReturn(IERC20Upgradeable token, bytes memory data) private { // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that // the target address contains contract code and also asserts for success in the low-level call. bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed"); require(returndata.length == 0 || abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); } /** * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement * on the return value: the return value is optional (but if data is returned, it must not be false). * @param token The token targeted by the call. * @param data The call data (encoded using abi.encode or one of its variants). * * This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead. */ function _callOptionalReturnBool(IERC20Upgradeable token, bytes memory data) private returns (bool) { // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since // we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false // and not revert is the subcall reverts. (bool success, bytes memory returndata) = address(token).call(data); return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && AddressUpgradeable.isContract(address(token)); } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC721/ERC721.sol) pragma solidity ^0.8.0; import "./IERC721Upgradeable.sol"; import "./IERC721ReceiverUpgradeable.sol"; import "./extensions/IERC721MetadataUpgradeable.sol"; import "../../utils/AddressUpgradeable.sol"; import "../../utils/ContextUpgradeable.sol"; import "../../utils/StringsUpgradeable.sol"; import "../../utils/introspection/ERC165Upgradeable.sol"; import {Initializable} from "../../proxy/utils/Initializable.sol"; /** * @dev Implementation of https://eips.ethereum.org/EIPS/eip-721[ERC721] Non-Fungible Token Standard, including * the Metadata extension, but not including the Enumerable extension, which is available separately as * {ERC721Enumerable}. */ contract ERC721Upgradeable is Initializable, ContextUpgradeable, ERC165Upgradeable, IERC721Upgradeable, IERC721MetadataUpgradeable { using AddressUpgradeable for address; using StringsUpgradeable for uint256; // Token name string private _name; // Token symbol string private _symbol; // Mapping from token ID to owner address mapping(uint256 => address) private _owners; // Mapping owner address to token count mapping(address => uint256) private _balances; // Mapping from token ID to approved address mapping(uint256 => address) private _tokenApprovals; // Mapping from owner to operator approvals mapping(address => mapping(address => bool)) private _operatorApprovals; /** * @dev Initializes the contract by setting a `name` and a `symbol` to the token collection. */ function __ERC721_init(string memory name_, string memory symbol_) internal onlyInitializing { __ERC721_init_unchained(name_, symbol_); } function __ERC721_init_unchained(string memory name_, string memory symbol_) internal onlyInitializing { _name = name_; _symbol = symbol_; } /** * @dev See {IERC165-supportsInterface}. */ function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165Upgradeable, IERC165Upgradeable) returns (bool) { return interfaceId == type(IERC721Upgradeable).interfaceId || interfaceId == type(IERC721MetadataUpgradeable).interfaceId || super.supportsInterface(interfaceId); } /** * @dev See {IERC721-balanceOf}. */ function balanceOf(address owner) public view virtual override returns (uint256) { require(owner != address(0), "ERC721: address zero is not a valid owner"); return _balances[owner]; } /** * @dev See {IERC721-ownerOf}. */ function ownerOf(uint256 tokenId) public view virtual override returns (address) { address owner = _ownerOf(tokenId); require(owner != address(0), "ERC721: invalid token ID"); return owner; } /** * @dev See {IERC721Metadata-name}. */ function name() public view virtual override returns (string memory) { return _name; } /** * @dev See {IERC721Metadata-symbol}. */ function symbol() public view virtual override returns (string memory) { return _symbol; } /** * @dev See {IERC721Metadata-tokenURI}. */ function tokenURI(uint256 tokenId) public view virtual override returns (string memory) { _requireMinted(tokenId); string memory baseURI = _baseURI(); return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : ""; } /** * @dev Base URI for computing {tokenURI}. If set, the resulting URI for each * token will be the concatenation of the `baseURI` and the `tokenId`. Empty * by default, can be overridden in child contracts. */ function _baseURI() internal view virtual returns (string memory) { return ""; } /** * @dev See {IERC721-approve}. */ function approve(address to, uint256 tokenId) public virtual override { address owner = ERC721Upgradeable.ownerOf(tokenId); require(to != owner, "ERC721: approval to current owner"); require( _msgSender() == owner || isApprovedForAll(owner, _msgSender()), "ERC721: approve caller is not token owner or approved for all" ); _approve(to, tokenId); } /** * @dev See {IERC721-getApproved}. */ function getApproved(uint256 tokenId) public view virtual override returns (address) { _requireMinted(tokenId); return _tokenApprovals[tokenId]; } /** * @dev See {IERC721-setApprovalForAll}. */ function setApprovalForAll(address operator, bool approved) public virtual override { _setApprovalForAll(_msgSender(), operator, approved); } /** * @dev See {IERC721-isApprovedForAll}. */ function isApprovedForAll(address owner, address operator) public view virtual override returns (bool) { return _operatorApprovals[owner][operator]; } /** * @dev See {IERC721-transferFrom}. */ function transferFrom(address from, address to, uint256 tokenId) public virtual override { //solhint-disable-next-line max-line-length require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner or approved"); _transfer(from, to, tokenId); } /** * @dev See {IERC721-safeTransferFrom}. */ function safeTransferFrom(address from, address to, uint256 tokenId) public virtual override { safeTransferFrom(from, to, tokenId, ""); } /** * @dev See {IERC721-safeTransferFrom}. */ function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public virtual override { require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner or approved"); _safeTransfer(from, to, tokenId, data); } /** * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients * are aware of the ERC721 protocol to prevent tokens from being forever locked. * * `data` is additional data, it has no specified format and it is sent in call to `to`. * * This internal function is equivalent to {safeTransferFrom}, and can be used to e.g. * implement alternative mechanisms to perform token transfer, such as signature-based. * * Requirements: * * - `from` cannot be the zero address. * - `to` cannot be the zero address. * - `tokenId` token must exist and be owned by `from`. * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. * * Emits a {Transfer} event. */ function _safeTransfer(address from, address to, uint256 tokenId, bytes memory data) internal virtual { _transfer(from, to, tokenId); require(_checkOnERC721Received(from, to, tokenId, data), "ERC721: transfer to non ERC721Receiver implementer"); } /** * @dev Returns the owner of the `tokenId`. Does NOT revert if token doesn't exist */ function _ownerOf(uint256 tokenId) internal view virtual returns (address) { return _owners[tokenId]; } /** * @dev Returns whether `tokenId` exists. * * Tokens can be managed by their owner or approved accounts via {approve} or {setApprovalForAll}. * * Tokens start existing when they are minted (`_mint`), * and stop existing when they are burned (`_burn`). */ function _exists(uint256 tokenId) internal view virtual returns (bool) { return _ownerOf(tokenId) != address(0); } /** * @dev Returns whether `spender` is allowed to manage `tokenId`. * * Requirements: * * - `tokenId` must exist. */ function _isApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns (bool) { address owner = ERC721Upgradeable.ownerOf(tokenId); return (spender == owner || isApprovedForAll(owner, spender) || getApproved(tokenId) == spender); } /** * @dev Safely mints `tokenId` and transfers it to `to`. * * Requirements: * * - `tokenId` must not exist. * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. * * Emits a {Transfer} event. */ function _safeMint(address to, uint256 tokenId) internal virtual { _safeMint(to, tokenId, ""); } /** * @dev Same as {xref-ERC721-_safeMint-address-uint256-}[`_safeMint`], with an additional `data` parameter which is * forwarded in {IERC721Receiver-onERC721Received} to contract recipients. */ function _safeMint(address to, uint256 tokenId, bytes memory data) internal virtual { _mint(to, tokenId); require( _checkOnERC721Received(address(0), to, tokenId, data), "ERC721: transfer to non ERC721Receiver implementer" ); } /** * @dev Mints `tokenId` and transfers it to `to`. * * WARNING: Usage of this method is discouraged, use {_safeMint} whenever possible * * Requirements: * * - `tokenId` must not exist. * - `to` cannot be the zero address. * * Emits a {Transfer} event. */ function _mint(address to, uint256 tokenId) internal virtual { require(to != address(0), "ERC721: mint to the zero address"); require(!_exists(tokenId), "ERC721: token already minted"); _beforeTokenTransfer(address(0), to, tokenId, 1); // Check that tokenId was not minted by `_beforeTokenTransfer` hook require(!_exists(tokenId), "ERC721: token already minted"); unchecked { // Will not overflow unless all 2**256 token ids are minted to the same owner. // Given that tokens are minted one by one, it is impossible in practice that // this ever happens. Might change if we allow batch minting. // The ERC fails to describe this case. _balances[to] += 1; } _owners[tokenId] = to; emit Transfer(address(0), to, tokenId); _afterTokenTransfer(address(0), to, tokenId, 1); } /** * @dev Destroys `tokenId`. * The approval is cleared when the token is burned. * This is an internal function that does not check if the sender is authorized to operate on the token. * * Requirements: * * - `tokenId` must exist. * * Emits a {Transfer} event. */ function _burn(uint256 tokenId) internal virtual { address owner = ERC721Upgradeable.ownerOf(tokenId); _beforeTokenTransfer(owner, address(0), tokenId, 1); // Update ownership in case tokenId was transferred by `_beforeTokenTransfer` hook owner = ERC721Upgradeable.ownerOf(tokenId); // Clear approvals delete _tokenApprovals[tokenId]; unchecked { // Cannot overflow, as that would require more tokens to be burned/transferred // out than the owner initially received through minting and transferring in. _balances[owner] -= 1; } delete _owners[tokenId]; emit Transfer(owner, address(0), tokenId); _afterTokenTransfer(owner, address(0), tokenId, 1); } /** * @dev Transfers `tokenId` from `from` to `to`. * As opposed to {transferFrom}, this imposes no restrictions on msg.sender. * * Requirements: * * - `to` cannot be the zero address. * - `tokenId` token must be owned by `from`. * * Emits a {Transfer} event. */ function _transfer(address from, address to, uint256 tokenId) internal virtual { require(ERC721Upgradeable.ownerOf(tokenId) == from, "ERC721: transfer from incorrect owner"); require(to != address(0), "ERC721: transfer to the zero address"); _beforeTokenTransfer(from, to, tokenId, 1); // Check that tokenId was not transferred by `_beforeTokenTransfer` hook require(ERC721Upgradeable.ownerOf(tokenId) == from, "ERC721: transfer from incorrect owner"); // Clear approvals from the previous owner delete _tokenApprovals[tokenId]; unchecked { // `_balances[from]` cannot overflow for the same reason as described in `_burn`: // `from`'s balance is the number of token held, which is at least one before the current // transfer. // `_balances[to]` could overflow in the conditions described in `_mint`. That would require // all 2**256 token ids to be minted, which in practice is impossible. _balances[from] -= 1; _balances[to] += 1; } _owners[tokenId] = to; emit Transfer(from, to, tokenId); _afterTokenTransfer(from, to, tokenId, 1); } /** * @dev Approve `to` to operate on `tokenId` * * Emits an {Approval} event. */ function _approve(address to, uint256 tokenId) internal virtual { _tokenApprovals[tokenId] = to; emit Approval(ERC721Upgradeable.ownerOf(tokenId), to, tokenId); } /** * @dev Approve `operator` to operate on all of `owner` tokens * * Emits an {ApprovalForAll} event. */ function _setApprovalForAll(address owner, address operator, bool approved) internal virtual { require(owner != operator, "ERC721: approve to caller"); _operatorApprovals[owner][operator] = approved; emit ApprovalForAll(owner, operator, approved); } /** * @dev Reverts if the `tokenId` has not been minted yet. */ function _requireMinted(uint256 tokenId) internal view virtual { require(_exists(tokenId), "ERC721: invalid token ID"); } /** * @dev Internal function to invoke {IERC721Receiver-onERC721Received} on a target address. * The call is not executed if the target address is not a contract. * * @param from address representing the previous owner of the given token ID * @param to target address that will receive the tokens * @param tokenId uint256 ID of the token to be transferred * @param data bytes optional data to send along with the call * @return bool whether the call correctly returned the expected magic value */ function _checkOnERC721Received( address from, address to, uint256 tokenId, bytes memory data ) private returns (bool) { if (to.isContract()) { try IERC721ReceiverUpgradeable(to).onERC721Received(_msgSender(), from, tokenId, data) returns (bytes4 retval) { return retval == IERC721ReceiverUpgradeable.onERC721Received.selector; } catch (bytes memory reason) { if (reason.length == 0) { revert("ERC721: transfer to non ERC721Receiver implementer"); } else { /// @solidity memory-safe-assembly assembly { revert(add(32, reason), mload(reason)) } } } } else { return true; } } /** * @dev Hook that is called before any token transfer. This includes minting and burning. If {ERC721Consecutive} is * used, the hook may be called as part of a consecutive (batch) mint, as indicated by `batchSize` greater than 1. * * Calling conditions: * * - When `from` and `to` are both non-zero, ``from``'s tokens will be transferred to `to`. * - When `from` is zero, the tokens will be minted for `to`. * - When `to` is zero, ``from``'s tokens will be burned. * - `from` and `to` are never both zero. * - `batchSize` is non-zero. * * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. */ function _beforeTokenTransfer(address from, address to, uint256 firstTokenId, uint256 batchSize) internal virtual {} /** * @dev Hook that is called after any token transfer. This includes minting and burning. If {ERC721Consecutive} is * used, the hook may be called as part of a consecutive (batch) mint, as indicated by `batchSize` greater than 1. * * Calling conditions: * * - When `from` and `to` are both non-zero, ``from``'s tokens were transferred to `to`. * - When `from` is zero, the tokens were minted for `to`. * - When `to` is zero, ``from``'s tokens were burned. * - `from` and `to` are never both zero. * - `batchSize` is non-zero. * * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. */ function _afterTokenTransfer(address from, address to, uint256 firstTokenId, uint256 batchSize) internal virtual {} /** * @dev Unsafe write access to the balances, used by extensions that "mint" tokens using an {ownerOf} override. * * WARNING: Anyone calling this MUST ensure that the balances remain consistent with the ownership. The invariant * being that for any address `a` the value returned by `balanceOf(a)` must be equal to the number of tokens such * that `ownerOf(tokenId)` is `a`. */ // solhint-disable-next-line func-name-mixedcase function __unsafe_increaseBalance(address account, uint256 amount) internal { _balances[account] += amount; } /** * @dev This empty reserved space is put in place to allow future versions to add new * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ uint256[44] private __gap; }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.8.0) (token/ERC721/extensions/ERC721Enumerable.sol) pragma solidity ^0.8.0; import "../ERC721Upgradeable.sol"; import "./IERC721EnumerableUpgradeable.sol"; import {Initializable} from "../../../proxy/utils/Initializable.sol"; /** * @dev This implements an optional extension of {ERC721} defined in the EIP that adds * enumerability of all the token ids in the contract as well as all token ids owned by each * account. */ abstract contract ERC721EnumerableUpgradeable is Initializable, ERC721Upgradeable, IERC721EnumerableUpgradeable { // Mapping from owner to list of owned token IDs mapping(address => mapping(uint256 => uint256)) private _ownedTokens; // Mapping from token ID to index of the owner tokens list mapping(uint256 => uint256) private _ownedTokensIndex; // Array with all token ids, used for enumeration uint256[] private _allTokens; // Mapping from token id to position in the allTokens array mapping(uint256 => uint256) private _allTokensIndex; function __ERC721Enumerable_init() internal onlyInitializing { } function __ERC721Enumerable_init_unchained() internal onlyInitializing { } /** * @dev See {IERC165-supportsInterface}. */ function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165Upgradeable, ERC721Upgradeable) returns (bool) { return interfaceId == type(IERC721EnumerableUpgradeable).interfaceId || super.supportsInterface(interfaceId); } /** * @dev See {IERC721Enumerable-tokenOfOwnerByIndex}. */ function tokenOfOwnerByIndex(address owner, uint256 index) public view virtual override returns (uint256) { require(index < ERC721Upgradeable.balanceOf(owner), "ERC721Enumerable: owner index out of bounds"); return _ownedTokens[owner][index]; } /** * @dev See {IERC721Enumerable-totalSupply}. */ function totalSupply() public view virtual override returns (uint256) { return _allTokens.length; } /** * @dev See {IERC721Enumerable-tokenByIndex}. */ function tokenByIndex(uint256 index) public view virtual override returns (uint256) { require(index < ERC721EnumerableUpgradeable.totalSupply(), "ERC721Enumerable: global index out of bounds"); return _allTokens[index]; } /** * @dev See {ERC721-_beforeTokenTransfer}. */ function _beforeTokenTransfer( address from, address to, uint256 firstTokenId, uint256 batchSize ) internal virtual override { super._beforeTokenTransfer(from, to, firstTokenId, batchSize); if (batchSize > 1) { // Will only trigger during construction. Batch transferring (minting) is not available afterwards. revert("ERC721Enumerable: consecutive transfers not supported"); } uint256 tokenId = firstTokenId; if (from == address(0)) { _addTokenToAllTokensEnumeration(tokenId); } else if (from != to) { _removeTokenFromOwnerEnumeration(from, tokenId); } if (to == address(0)) { _removeTokenFromAllTokensEnumeration(tokenId); } else if (to != from) { _addTokenToOwnerEnumeration(to, tokenId); } } /** * @dev Private function to add a token to this extension's ownership-tracking data structures. * @param to address representing the new owner of the given token ID * @param tokenId uint256 ID of the token to be added to the tokens list of the given address */ function _addTokenToOwnerEnumeration(address to, uint256 tokenId) private { uint256 length = ERC721Upgradeable.balanceOf(to); _ownedTokens[to][length] = tokenId; _ownedTokensIndex[tokenId] = length; } /** * @dev Private function to add a token to this extension's token tracking data structures. * @param tokenId uint256 ID of the token to be added to the tokens list */ function _addTokenToAllTokensEnumeration(uint256 tokenId) private { _allTokensIndex[tokenId] = _allTokens.length; _allTokens.push(tokenId); } /** * @dev Private function to remove a token from this extension's ownership-tracking data structures. Note that * while the token is not assigned a new owner, the `_ownedTokensIndex` mapping is _not_ updated: this allows for * gas optimizations e.g. when performing a transfer operation (avoiding double writes). * This has O(1) time complexity, but alters the order of the _ownedTokens array. * @param from address representing the previous owner of the given token ID * @param tokenId uint256 ID of the token to be removed from the tokens list of the given address */ function _removeTokenFromOwnerEnumeration(address from, uint256 tokenId) private { // To prevent a gap in from's tokens array, we store the last token in the index of the token to delete, and // then delete the last slot (swap and pop). uint256 lastTokenIndex = ERC721Upgradeable.balanceOf(from) - 1; uint256 tokenIndex = _ownedTokensIndex[tokenId]; // When the token to delete is the last token, the swap operation is unnecessary if (tokenIndex != lastTokenIndex) { uint256 lastTokenId = _ownedTokens[from][lastTokenIndex]; _ownedTokens[from][tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token _ownedTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index } // This also deletes the contents at the last position of the array delete _ownedTokensIndex[tokenId]; delete _ownedTokens[from][lastTokenIndex]; } /** * @dev Private function to remove a token from this extension's token tracking data structures. * This has O(1) time complexity, but alters the order of the _allTokens array. * @param tokenId uint256 ID of the token to be removed from the tokens list */ function _removeTokenFromAllTokensEnumeration(uint256 tokenId) private { // To prevent a gap in the tokens array, we store the last token in the index of the token to delete, and // then delete the last slot (swap and pop). uint256 lastTokenIndex = _allTokens.length - 1; uint256 tokenIndex = _allTokensIndex[tokenId]; // When the token to delete is the last token, the swap operation is unnecessary. However, since this occurs so // rarely (when the last minted token is burnt) that we still do the swap here to avoid the gas cost of adding // an 'if' statement (like in _removeTokenFromOwnerEnumeration) uint256 lastTokenId = _allTokens[lastTokenIndex]; _allTokens[tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token _allTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index // This also deletes the contents at the last position of the array delete _allTokensIndex[tokenId]; _allTokens.pop(); } /** * @dev This empty reserved space is put in place to allow future versions to add new * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ uint256[46] private __gap; }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.5.0) (token/ERC721/extensions/IERC721Enumerable.sol) pragma solidity ^0.8.0; import "../IERC721Upgradeable.sol"; /** * @title ERC-721 Non-Fungible Token Standard, optional enumeration extension * @dev See https://eips.ethereum.org/EIPS/eip-721 */ interface IERC721EnumerableUpgradeable is IERC721Upgradeable { /** * @dev Returns the total amount of tokens stored by the contract. */ function totalSupply() external view returns (uint256); /** * @dev Returns a token ID owned by `owner` at a given `index` of its token list. * Use along with {balanceOf} to enumerate all of ``owner``'s tokens. */ function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256); /** * @dev Returns a token ID at a given `index` of all the tokens stored by the contract. * Use along with {totalSupply} to enumerate all tokens. */ function tokenByIndex(uint256 index) external view returns (uint256); }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (token/ERC721/extensions/IERC721Metadata.sol) pragma solidity ^0.8.0; import "../IERC721Upgradeable.sol"; /** * @title ERC-721 Non-Fungible Token Standard, optional metadata extension * @dev See https://eips.ethereum.org/EIPS/eip-721 */ interface IERC721MetadataUpgradeable is IERC721Upgradeable { /** * @dev Returns the token collection name. */ function name() external view returns (string memory); /** * @dev Returns the token collection symbol. */ function symbol() external view returns (string memory); /** * @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token. */ function tokenURI(uint256 tokenId) external view returns (string memory); }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.6.0) (token/ERC721/IERC721Receiver.sol) pragma solidity ^0.8.0; /** * @title ERC721 token receiver interface * @dev Interface for any contract that wants to support safeTransfers * from ERC721 asset contracts. */ interface IERC721ReceiverUpgradeable { /** * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom} * by `operator` from `from`, this function is called. * * It must return its Solidity selector to confirm the token transfer. * If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted. * * The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`. */ function onERC721Received( address operator, address from, uint256 tokenId, bytes calldata data ) external returns (bytes4); }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC721/IERC721.sol) pragma solidity ^0.8.0; import "../../utils/introspection/IERC165Upgradeable.sol"; /** * @dev Required interface of an ERC721 compliant contract. */ interface IERC721Upgradeable is IERC165Upgradeable { /** * @dev Emitted when `tokenId` token is transferred from `from` to `to`. */ event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); /** * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token. */ event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); /** * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets. */ event ApprovalForAll(address indexed owner, address indexed operator, bool approved); /** * @dev Returns the number of tokens in ``owner``'s account. */ function balanceOf(address owner) external view returns (uint256 balance); /** * @dev Returns the owner of the `tokenId` token. * * Requirements: * * - `tokenId` must exist. */ function ownerOf(uint256 tokenId) external view returns (address owner); /** * @dev Safely transfers `tokenId` token from `from` to `to`. * * Requirements: * * - `from` cannot be the zero address. * - `to` cannot be the zero address. * - `tokenId` token must exist and be owned by `from`. * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}. * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. * * Emits a {Transfer} event. */ function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external; /** * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients * are aware of the ERC721 protocol to prevent tokens from being forever locked. * * Requirements: * * - `from` cannot be the zero address. * - `to` cannot be the zero address. * - `tokenId` token must exist and be owned by `from`. * - If the caller is not `from`, it must have been allowed to move this token by either {approve} or {setApprovalForAll}. * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. * * Emits a {Transfer} event. */ function safeTransferFrom(address from, address to, uint256 tokenId) external; /** * @dev Transfers `tokenId` token from `from` to `to`. * * WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721 * or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must * understand this adds an external call which potentially creates a reentrancy vulnerability. * * Requirements: * * - `from` cannot be the zero address. * - `to` cannot be the zero address. * - `tokenId` token must be owned by `from`. * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}. * * Emits a {Transfer} event. */ function transferFrom(address from, address to, uint256 tokenId) external; /** * @dev Gives permission to `to` to transfer `tokenId` token to another account. * The approval is cleared when the token is transferred. * * Only a single account can be approved at a time, so approving the zero address clears previous approvals. * * Requirements: * * - The caller must own the token or be an approved operator. * - `tokenId` must exist. * * Emits an {Approval} event. */ function approve(address to, uint256 tokenId) external; /** * @dev Approve or remove `operator` as an operator for the caller. * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller. * * Requirements: * * - The `operator` cannot be the caller. * * Emits an {ApprovalForAll} event. */ function setApprovalForAll(address operator, bool approved) external; /** * @dev Returns the account approved for `tokenId` token. * * Requirements: * * - `tokenId` must exist. */ function getApproved(uint256 tokenId) external view returns (address operator); /** * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`. * * See {setApprovalForAll} */ function isApprovedForAll(address owner, address operator) external view returns (bool); }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol) pragma solidity ^0.8.1; /** * @dev Collection of functions related to the address type */ library AddressUpgradeable { /** * @dev Returns true if `account` is a contract. * * [IMPORTANT] * ==== * It is unsafe to assume that an address for which this function returns * false is an externally-owned account (EOA) and not a contract. * * Among others, `isContract` will return false for the following * types of addresses: * * - an externally-owned account * - a contract in construction * - an address where a contract will be created * - an address where a contract lived, but was destroyed * * Furthermore, `isContract` will also return true if the target contract within * the same transaction is already scheduled for destruction by `SELFDESTRUCT`, * which only has an effect at the end of a transaction. * ==== * * [IMPORTANT] * ==== * You shouldn't rely on `isContract` to protect against flash loan attacks! * * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract * constructor. * ==== */ function isContract(address account) internal view returns (bool) { // This method relies on extcodesize/address.code.length, which returns 0 // for contracts in construction, since the code is only stored at the end // of the constructor execution. return account.code.length > 0; } /** * @dev Replacement for Solidity's `transfer`: sends `amount` wei to * `recipient`, forwarding all available gas and reverting on errors. * * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost * of certain opcodes, possibly making contracts go over the 2300 gas limit * imposed by `transfer`, making them unable to receive funds via * `transfer`. {sendValue} removes this limitation. * * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more]. * * IMPORTANT: because control is transferred to `recipient`, care must be * taken to not create reentrancy vulnerabilities. Consider using * {ReentrancyGuard} or the * https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. */ function sendValue(address payable recipient, uint256 amount) internal { require(address(this).balance >= amount, "Address: insufficient balance"); (bool success, ) = recipient.call{value: amount}(""); require(success, "Address: unable to send value, recipient may have reverted"); } /** * @dev Performs a Solidity function call using a low level `call`. A * plain `call` is an unsafe replacement for a function call: use this * function instead. * * If `target` reverts with a revert reason, it is bubbled up by this * function (like regular Solidity function calls). * * Returns the raw returned data. To convert to the expected return value, * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. * * Requirements: * * - `target` must be a contract. * - calling `target` with `data` must not revert. * * _Available since v3.1._ */ function functionCall(address target, bytes memory data) internal returns (bytes memory) { return functionCallWithValue(target, data, 0, "Address: low-level call failed"); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with * `errorMessage` as a fallback revert reason when `target` reverts. * * _Available since v3.1._ */ function functionCall( address target, bytes memory data, string memory errorMessage ) internal returns (bytes memory) { return functionCallWithValue(target, data, 0, errorMessage); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but also transferring `value` wei to `target`. * * Requirements: * * - the calling contract must have an ETH balance of at least `value`. * - the called Solidity function must be `payable`. * * _Available since v3.1._ */ function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) { return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); } /** * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but * with `errorMessage` as a fallback revert reason when `target` reverts. * * _Available since v3.1._ */ function functionCallWithValue( address target, bytes memory data, uint256 value, string memory errorMessage ) internal returns (bytes memory) { require(address(this).balance >= value, "Address: insufficient balance for call"); (bool success, bytes memory returndata) = target.call{value: value}(data); return verifyCallResultFromTarget(target, success, returndata, errorMessage); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but performing a static call. * * _Available since v3.3._ */ function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { return functionStaticCall(target, data, "Address: low-level static call failed"); } /** * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], * but performing a static call. * * _Available since v3.3._ */ function functionStaticCall( address target, bytes memory data, string memory errorMessage ) internal view returns (bytes memory) { (bool success, bytes memory returndata) = target.staticcall(data); return verifyCallResultFromTarget(target, success, returndata, errorMessage); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but performing a delegate call. * * _Available since v3.4._ */ function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { return functionDelegateCall(target, data, "Address: low-level delegate call failed"); } /** * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], * but performing a delegate call. * * _Available since v3.4._ */ function functionDelegateCall( address target, bytes memory data, string memory errorMessage ) internal returns (bytes memory) { (bool success, bytes memory returndata) = target.delegatecall(data); return verifyCallResultFromTarget(target, success, returndata, errorMessage); } /** * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract. * * _Available since v4.8._ */ function verifyCallResultFromTarget( address target, bool success, bytes memory returndata, string memory errorMessage ) internal view returns (bytes memory) { if (success) { if (returndata.length == 0) { // only check isContract if the call was successful and the return data is empty // otherwise we already know that it was a contract require(isContract(target), "Address: call to non-contract"); } return returndata; } else { _revert(returndata, errorMessage); } } /** * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the * revert reason or using the provided one. * * _Available since v4.3._ */ function verifyCallResult( bool success, bytes memory returndata, string memory errorMessage ) internal pure returns (bytes memory) { if (success) { return returndata; } else { _revert(returndata, errorMessage); } } function _revert(bytes memory returndata, string memory errorMessage) private pure { // Look for revert reason and bubble it up if present if (returndata.length > 0) { // The easiest way to bubble the revert reason is using memory via assembly /// @solidity memory-safe-assembly assembly { let returndata_size := mload(returndata) revert(add(32, returndata), returndata_size) } } else { revert(errorMessage); } } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.4) (utils/Context.sol) pragma solidity ^0.8.0; import {Initializable} from "../proxy/utils/Initializable.sol"; /** * @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 ContextUpgradeable is Initializable { function __Context_init() internal onlyInitializing { } function __Context_init_unchained() internal onlyInitializing { } function _msgSender() internal view virtual returns (address) { return msg.sender; } function _msgData() internal view virtual returns (bytes calldata) { return msg.data; } function _contextSuffixLength() internal view virtual returns (uint256) { return 0; } /** * @dev This empty reserved space is put in place to allow future versions to add new * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ uint256[50] private __gap; }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (utils/Counters.sol) pragma solidity ^0.8.0; /** * @title Counters * @author Matt Condon (@shrugs) * @dev Provides counters that can only be incremented, decremented or reset. This can be used e.g. to track the number * of elements in a mapping, issuing ERC721 ids, or counting request ids. * * Include with `using Counters for Counters.Counter;` */ library CountersUpgradeable { struct Counter { // This variable should never be directly accessed by users of the library: interactions must be restricted to // the library's function. As of Solidity v0.5.2, this cannot be enforced, though there is a proposal to add // this feature: see https://github.com/ethereum/solidity/issues/4637 uint256 _value; // default: 0 } function current(Counter storage counter) internal view returns (uint256) { return counter._value; } function increment(Counter storage counter) internal { unchecked { counter._value += 1; } } function decrement(Counter storage counter) internal { uint256 value = counter._value; require(value > 0, "Counter: decrement overflow"); unchecked { counter._value = value - 1; } } function reset(Counter storage counter) internal { counter._value = 0; } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (utils/cryptography/ECDSA.sol) pragma solidity ^0.8.0; import "../StringsUpgradeable.sol"; /** * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations. * * These functions can be used to verify that a message was signed by the holder * of the private keys of a given address. */ library ECDSAUpgradeable { enum RecoverError { NoError, InvalidSignature, InvalidSignatureLength, InvalidSignatureS, InvalidSignatureV // Deprecated in v4.8 } function _throwError(RecoverError error) private pure { if (error == RecoverError.NoError) { return; // no error: do nothing } else if (error == RecoverError.InvalidSignature) { revert("ECDSA: invalid signature"); } else if (error == RecoverError.InvalidSignatureLength) { revert("ECDSA: invalid signature length"); } else if (error == RecoverError.InvalidSignatureS) { revert("ECDSA: invalid signature 's' value"); } } /** * @dev Returns the address that signed a hashed message (`hash`) with * `signature` or error string. This address can then be used for verification purposes. * * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures: * this function rejects them by requiring the `s` value to be in the lower * half order, and the `v` value to be either 27 or 28. * * IMPORTANT: `hash` _must_ be the result of a hash operation for the * verification to be secure: it is possible to craft signatures that * recover to arbitrary addresses for non-hashed data. A safe way to ensure * this is by receiving a hash of the original message (which may otherwise * be too long), and then calling {toEthSignedMessageHash} on it. * * Documentation for signature generation: * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js] * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers] * * _Available since v4.3._ */ function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) { if (signature.length == 65) { bytes32 r; bytes32 s; uint8 v; // ecrecover takes the signature parameters, and the only way to get them // currently is to use assembly. /// @solidity memory-safe-assembly assembly { r := mload(add(signature, 0x20)) s := mload(add(signature, 0x40)) v := byte(0, mload(add(signature, 0x60))) } return tryRecover(hash, v, r, s); } else { return (address(0), RecoverError.InvalidSignatureLength); } } /** * @dev Returns the address that signed a hashed message (`hash`) with * `signature`. This address can then be used for verification purposes. * * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures: * this function rejects them by requiring the `s` value to be in the lower * half order, and the `v` value to be either 27 or 28. * * IMPORTANT: `hash` _must_ be the result of a hash operation for the * verification to be secure: it is possible to craft signatures that * recover to arbitrary addresses for non-hashed data. A safe way to ensure * this is by receiving a hash of the original message (which may otherwise * be too long), and then calling {toEthSignedMessageHash} on it. */ function recover(bytes32 hash, bytes memory signature) internal pure returns (address) { (address recovered, RecoverError error) = tryRecover(hash, signature); _throwError(error); return recovered; } /** * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately. * * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures] * * _Available since v4.3._ */ function tryRecover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address, RecoverError) { bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff); uint8 v = uint8((uint256(vs) >> 255) + 27); return tryRecover(hash, v, r, s); } /** * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately. * * _Available since v4.2._ */ function recover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address) { (address recovered, RecoverError error) = tryRecover(hash, r, vs); _throwError(error); return recovered; } /** * @dev Overload of {ECDSA-tryRecover} that receives the `v`, * `r` and `s` signature fields separately. * * _Available since v4.3._ */ function tryRecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address, RecoverError) { // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most // signatures from current libraries generate a unique signature with an s-value in the lower half order. // // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept // these malleable signatures as well. if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) { return (address(0), RecoverError.InvalidSignatureS); } // If the signature is valid (and not malleable), return the signer address address signer = ecrecover(hash, v, r, s); if (signer == address(0)) { return (address(0), RecoverError.InvalidSignature); } return (signer, RecoverError.NoError); } /** * @dev Overload of {ECDSA-recover} that receives the `v`, * `r` and `s` signature fields separately. */ function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) { (address recovered, RecoverError error) = tryRecover(hash, v, r, s); _throwError(error); return recovered; } /** * @dev Returns an Ethereum Signed Message, created from a `hash`. This * produces hash corresponding to the one signed with the * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`] * JSON-RPC method as part of EIP-191. * * See {recover}. */ function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32 message) { // 32 is the length in bytes of hash, // enforced by the type signature above /// @solidity memory-safe-assembly assembly { mstore(0x00, "\x19Ethereum Signed Message:\n32") mstore(0x1c, hash) message := keccak256(0x00, 0x3c) } } /** * @dev Returns an Ethereum Signed Message, created from `s`. This * produces hash corresponding to the one signed with the * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`] * JSON-RPC method as part of EIP-191. * * See {recover}. */ function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32) { return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n", StringsUpgradeable.toString(s.length), s)); } /** * @dev Returns an Ethereum Signed Typed Data, created from a * `domainSeparator` and a `structHash`. This produces hash corresponding * to the one signed with the * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`] * JSON-RPC method as part of EIP-712. * * See {recover}. */ function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32 data) { /// @solidity memory-safe-assembly assembly { let ptr := mload(0x40) mstore(ptr, "\x19\x01") mstore(add(ptr, 0x02), domainSeparator) mstore(add(ptr, 0x22), structHash) data := keccak256(ptr, 0x42) } } /** * @dev Returns an Ethereum Signed Data with intended validator, created from a * `validator` and `data` according to the version 0 of EIP-191. * * See {recover}. */ function toDataWithIntendedValidatorHash(address validator, bytes memory data) internal pure returns (bytes32) { return keccak256(abi.encodePacked("\x19\x00", validator, data)); } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (utils/cryptography/EIP712.sol) pragma solidity ^0.8.8; import "./ECDSAUpgradeable.sol"; import "../../interfaces/IERC5267Upgradeable.sol"; import {Initializable} from "../../proxy/utils/Initializable.sol"; /** * @dev https://eips.ethereum.org/EIPS/eip-712[EIP 712] is a standard for hashing and signing of typed structured data. * * The encoding specified in the EIP is very generic, and such a generic implementation in Solidity is not feasible, * thus this contract does not implement the encoding itself. Protocols need to implement the type-specific encoding * they need in their contracts using a combination of `abi.encode` and `keccak256`. * * This contract implements the EIP 712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding * scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA * ({_hashTypedDataV4}). * * The implementation of the domain separator was designed to be as efficient as possible while still properly updating * the chain id to protect against replay attacks on an eventual fork of the chain. * * NOTE: This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method * https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask]. * * NOTE: In the upgradeable version of this contract, the cached values will correspond to the address, and the domain * separator of the implementation contract. This will cause the `_domainSeparatorV4` function to always rebuild the * separator from the immutable values, which is cheaper than accessing a cached version in cold storage. * * _Available since v3.4._ * * @custom:storage-size 52 */ abstract contract EIP712Upgradeable is Initializable, IERC5267Upgradeable { bytes32 private constant _TYPE_HASH = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); /// @custom:oz-renamed-from _HASHED_NAME bytes32 private _hashedName; /// @custom:oz-renamed-from _HASHED_VERSION bytes32 private _hashedVersion; string private _name; string private _version; /** * @dev Initializes the domain separator and parameter caches. * * The meaning of `name` and `version` is specified in * https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP 712]: * * - `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol. * - `version`: the current major version of the signing domain. * * NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart * contract upgrade]. */ function __EIP712_init(string memory name, string memory version) internal onlyInitializing { __EIP712_init_unchained(name, version); } function __EIP712_init_unchained(string memory name, string memory version) internal onlyInitializing { _name = name; _version = version; // Reset prior values in storage if upgrading _hashedName = 0; _hashedVersion = 0; } /** * @dev Returns the domain separator for the current chain. */ function _domainSeparatorV4() internal view returns (bytes32) { return _buildDomainSeparator(); } function _buildDomainSeparator() private view returns (bytes32) { return keccak256(abi.encode(_TYPE_HASH, _EIP712NameHash(), _EIP712VersionHash(), block.chainid, address(this))); } /** * @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this * function returns the hash of the fully encoded EIP712 message for this domain. * * This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example: * * ```solidity * bytes32 digest = _hashTypedDataV4(keccak256(abi.encode( * keccak256("Mail(address to,string contents)"), * mailTo, * keccak256(bytes(mailContents)) * ))); * address signer = ECDSA.recover(digest, signature); * ``` */ function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) { return ECDSAUpgradeable.toTypedDataHash(_domainSeparatorV4(), structHash); } /** * @dev See {EIP-5267}. * * _Available since v4.9._ */ function eip712Domain() public view virtual override returns ( bytes1 fields, string memory name, string memory version, uint256 chainId, address verifyingContract, bytes32 salt, uint256[] memory extensions ) { // If the hashed name and version in storage are non-zero, the contract hasn't been properly initialized // and the EIP712 domain is not reliable, as it will be missing name and version. require(_hashedName == 0 && _hashedVersion == 0, "EIP712: Uninitialized"); return ( hex"0f", // 01111 _EIP712Name(), _EIP712Version(), block.chainid, address(this), bytes32(0), new uint256[](0) ); } /** * @dev The name parameter for the EIP712 domain. * * NOTE: This function reads from storage by default, but can be redefined to return a constant value if gas costs * are a concern. */ function _EIP712Name() internal virtual view returns (string memory) { return _name; } /** * @dev The version parameter for the EIP712 domain. * * NOTE: This function reads from storage by default, but can be redefined to return a constant value if gas costs * are a concern. */ function _EIP712Version() internal virtual view returns (string memory) { return _version; } /** * @dev The hash of the name parameter for the EIP712 domain. * * NOTE: In previous versions this function was virtual. In this version you should override `_EIP712Name` instead. */ function _EIP712NameHash() internal view returns (bytes32) { string memory name = _EIP712Name(); if (bytes(name).length > 0) { return keccak256(bytes(name)); } else { // If the name is empty, the contract may have been upgraded without initializing the new storage. // We return the name hash in storage if non-zero, otherwise we assume the name is empty by design. bytes32 hashedName = _hashedName; if (hashedName != 0) { return hashedName; } else { return keccak256(""); } } } /** * @dev The hash of the version parameter for the EIP712 domain. * * NOTE: In previous versions this function was virtual. In this version you should override `_EIP712Version` instead. */ function _EIP712VersionHash() internal view returns (bytes32) { string memory version = _EIP712Version(); if (bytes(version).length > 0) { return keccak256(bytes(version)); } else { // If the version is empty, the contract may have been upgraded without initializing the new storage. // We return the version hash in storage if non-zero, otherwise we assume the version is empty by design. bytes32 hashedVersion = _hashedVersion; if (hashedVersion != 0) { return hashedVersion; } else { return keccak256(""); } } } /** * @dev This empty reserved space is put in place to allow future versions to add new * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ uint256[48] private __gap; }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol) pragma solidity ^0.8.0; import "./IERC165Upgradeable.sol"; import {Initializable} from "../../proxy/utils/Initializable.sol"; /** * @dev Implementation of the {IERC165} interface. * * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check * for the additional interface id that will be supported. For example: * * ```solidity * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { * return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId); * } * ``` * * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation. */ abstract contract ERC165Upgradeable is Initializable, IERC165Upgradeable { function __ERC165_init() internal onlyInitializing { } function __ERC165_init_unchained() internal onlyInitializing { } /** * @dev See {IERC165-supportsInterface}. */ function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { return interfaceId == type(IERC165Upgradeable).interfaceId; } /** * @dev This empty reserved space is put in place to allow future versions to add new * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ uint256[50] private __gap; }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol) pragma solidity ^0.8.0; /** * @dev Interface of the ERC165 standard, as defined in the * https://eips.ethereum.org/EIPS/eip-165[EIP]. * * Implementers can declare support of contract interfaces, which can then be * queried by others ({ERC165Checker}). * * For an implementation, see {ERC165}. */ interface IERC165Upgradeable { /** * @dev Returns true if this contract implements the interface defined by * `interfaceId`. See the corresponding * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] * to learn more about how these ids are created. * * This function call must use less than 30 000 gas. */ function supportsInterface(bytes4 interfaceId) external view returns (bool); }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (utils/math/Math.sol) pragma solidity ^0.8.0; /** * @dev Standard math utilities missing in the Solidity language. */ library MathUpgradeable { enum Rounding { Down, // Toward negative infinity Up, // Toward infinity Zero // Toward zero } /** * @dev Returns the largest of two numbers. */ function max(uint256 a, uint256 b) internal pure returns (uint256) { return a > b ? a : b; } /** * @dev Returns the smallest of two numbers. */ function min(uint256 a, uint256 b) internal pure returns (uint256) { return a < b ? a : b; } /** * @dev Returns the average of two numbers. The result is rounded towards * zero. */ function average(uint256 a, uint256 b) internal pure returns (uint256) { // (a + b) / 2 can overflow. return (a & b) + (a ^ b) / 2; } /** * @dev Returns the ceiling of the division of two numbers. * * This differs from standard division with `/` in that it rounds up instead * of rounding down. */ function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) { // (a + b - 1) / b can overflow on addition, so we distribute. return a == 0 ? 0 : (a - 1) / b + 1; } /** * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0 * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) * with further edits by Uniswap Labs also under MIT license. */ function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) { unchecked { // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256 // variables such that product = prod1 * 2^256 + prod0. uint256 prod0; // Least significant 256 bits of the product uint256 prod1; // Most significant 256 bits of the product assembly { let mm := mulmod(x, y, not(0)) prod0 := mul(x, y) prod1 := sub(sub(mm, prod0), lt(mm, prod0)) } // Handle non-overflow cases, 256 by 256 division. if (prod1 == 0) { // Solidity will revert if denominator == 0, unlike the div opcode on its own. // The surrounding unchecked block does not change this fact. // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic. return prod0 / denominator; } // Make sure the result is less than 2^256. Also prevents denominator == 0. require(denominator > prod1, "Math: mulDiv overflow"); /////////////////////////////////////////////// // 512 by 256 division. /////////////////////////////////////////////// // Make division exact by subtracting the remainder from [prod1 prod0]. uint256 remainder; assembly { // Compute remainder using mulmod. remainder := mulmod(x, y, denominator) // Subtract 256 bit number from 512 bit number. prod1 := sub(prod1, gt(remainder, prod0)) prod0 := sub(prod0, remainder) } // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1. // See https://cs.stackexchange.com/q/138556/92363. // Does not overflow because the denominator cannot be zero at this stage in the function. uint256 twos = denominator & (~denominator + 1); assembly { // Divide denominator by twos. denominator := div(denominator, twos) // Divide [prod1 prod0] by twos. prod0 := div(prod0, twos) // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one. twos := add(div(sub(0, twos), twos), 1) } // Shift in bits from prod1 into prod0. prod0 |= prod1 * twos; // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for // four bits. That is, denominator * inv = 1 mod 2^4. uint256 inverse = (3 * denominator) ^ 2; // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works // in modular arithmetic, doubling the correct bits in each step. inverse *= 2 - denominator * inverse; // inverse mod 2^8 inverse *= 2 - denominator * inverse; // inverse mod 2^16 inverse *= 2 - denominator * inverse; // inverse mod 2^32 inverse *= 2 - denominator * inverse; // inverse mod 2^64 inverse *= 2 - denominator * inverse; // inverse mod 2^128 inverse *= 2 - denominator * inverse; // inverse mod 2^256 // Because the division is now exact we can divide by multiplying with the modular inverse of denominator. // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1 // is no longer required. result = prod0 * inverse; return result; } } /** * @notice Calculates x * y / denominator with full precision, following the selected rounding direction. */ function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) { uint256 result = mulDiv(x, y, denominator); if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) { result += 1; } return result; } /** * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down. * * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11). */ function sqrt(uint256 a) internal pure returns (uint256) { if (a == 0) { return 0; } // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target. // // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`. // // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)` // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))` // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)` // // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit. uint256 result = 1 << (log2(a) >> 1); // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128, // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision // into the expected uint128 result. unchecked { result = (result + a / result) >> 1; result = (result + a / result) >> 1; result = (result + a / result) >> 1; result = (result + a / result) >> 1; result = (result + a / result) >> 1; result = (result + a / result) >> 1; result = (result + a / result) >> 1; return min(result, a / result); } } /** * @notice Calculates sqrt(a), following the selected rounding direction. */ function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) { unchecked { uint256 result = sqrt(a); return result + (rounding == Rounding.Up && result * result < a ? 1 : 0); } } /** * @dev Return the log in base 2, rounded down, of a positive value. * Returns 0 if given 0. */ function log2(uint256 value) internal pure returns (uint256) { uint256 result = 0; unchecked { if (value >> 128 > 0) { value >>= 128; result += 128; } if (value >> 64 > 0) { value >>= 64; result += 64; } if (value >> 32 > 0) { value >>= 32; result += 32; } if (value >> 16 > 0) { value >>= 16; result += 16; } if (value >> 8 > 0) { value >>= 8; result += 8; } if (value >> 4 > 0) { value >>= 4; result += 4; } if (value >> 2 > 0) { value >>= 2; result += 2; } if (value >> 1 > 0) { result += 1; } } return result; } /** * @dev Return the log in base 2, following the selected rounding direction, of a positive value. * Returns 0 if given 0. */ function log2(uint256 value, Rounding rounding) internal pure returns (uint256) { unchecked { uint256 result = log2(value); return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0); } } /** * @dev Return the log in base 10, rounded down, of a positive value. * Returns 0 if given 0. */ function log10(uint256 value) internal pure returns (uint256) { uint256 result = 0; unchecked { if (value >= 10 ** 64) { value /= 10 ** 64; result += 64; } if (value >= 10 ** 32) { value /= 10 ** 32; result += 32; } if (value >= 10 ** 16) { value /= 10 ** 16; result += 16; } if (value >= 10 ** 8) { value /= 10 ** 8; result += 8; } if (value >= 10 ** 4) { value /= 10 ** 4; result += 4; } if (value >= 10 ** 2) { value /= 10 ** 2; result += 2; } if (value >= 10 ** 1) { result += 1; } } return result; } /** * @dev Return the log in base 10, following the selected rounding direction, of a positive value. * Returns 0 if given 0. */ function log10(uint256 value, Rounding rounding) internal pure returns (uint256) { unchecked { uint256 result = log10(value); return result + (rounding == Rounding.Up && 10 ** result < value ? 1 : 0); } } /** * @dev Return the log in base 256, rounded down, of a positive value. * Returns 0 if given 0. * * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string. */ function log256(uint256 value) internal pure returns (uint256) { uint256 result = 0; unchecked { if (value >> 128 > 0) { value >>= 128; result += 16; } if (value >> 64 > 0) { value >>= 64; result += 8; } if (value >> 32 > 0) { value >>= 32; result += 4; } if (value >> 16 > 0) { value >>= 16; result += 2; } if (value >> 8 > 0) { result += 1; } } return result; } /** * @dev Return the log in base 256, following the selected rounding direction, of a positive value. * Returns 0 if given 0. */ function log256(uint256 value, Rounding rounding) internal pure returns (uint256) { unchecked { uint256 result = log256(value); return result + (rounding == Rounding.Up && 1 << (result << 3) < value ? 1 : 0); } } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.8.0) (utils/math/SignedMath.sol) pragma solidity ^0.8.0; /** * @dev Standard signed math utilities missing in the Solidity language. */ library SignedMathUpgradeable { /** * @dev Returns the largest of two signed numbers. */ function max(int256 a, int256 b) internal pure returns (int256) { return a > b ? a : b; } /** * @dev Returns the smallest of two signed numbers. */ function min(int256 a, int256 b) internal pure returns (int256) { return a < b ? a : b; } /** * @dev Returns the average of two signed numbers without overflow. * The result is rounded towards zero. */ function average(int256 a, int256 b) internal pure returns (int256) { // Formula from the book "Hacker's Delight" int256 x = (a & b) + ((a ^ b) >> 1); return x + (int256(uint256(x) >> 255) & (a ^ b)); } /** * @dev Returns the absolute unsigned value of a signed value. */ function abs(int256 n) internal pure returns (uint256) { unchecked { // must be unchecked in order to support `n = type(int256).min` return uint256(n >= 0 ? n : -n); } } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (utils/Strings.sol) pragma solidity ^0.8.0; import "./math/MathUpgradeable.sol"; import "./math/SignedMathUpgradeable.sol"; /** * @dev String operations. */ library StringsUpgradeable { bytes16 private constant _SYMBOLS = "0123456789abcdef"; uint8 private constant _ADDRESS_LENGTH = 20; /** * @dev Converts a `uint256` to its ASCII `string` decimal representation. */ function toString(uint256 value) internal pure returns (string memory) { unchecked { uint256 length = MathUpgradeable.log10(value) + 1; string memory buffer = new string(length); uint256 ptr; /// @solidity memory-safe-assembly assembly { ptr := add(buffer, add(32, length)) } while (true) { ptr--; /// @solidity memory-safe-assembly assembly { mstore8(ptr, byte(mod(value, 10), _SYMBOLS)) } value /= 10; if (value == 0) break; } return buffer; } } /** * @dev Converts a `int256` to its ASCII `string` decimal representation. */ function toString(int256 value) internal pure returns (string memory) { return string(abi.encodePacked(value < 0 ? "-" : "", toString(SignedMathUpgradeable.abs(value)))); } /** * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation. */ function toHexString(uint256 value) internal pure returns (string memory) { unchecked { return toHexString(value, MathUpgradeable.log256(value) + 1); } } /** * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length. */ function toHexString(uint256 value, uint256 length) internal pure returns (string memory) { bytes memory buffer = new bytes(2 * length + 2); buffer[0] = "0"; buffer[1] = "x"; for (uint256 i = 2 * length + 1; i > 1; --i) { buffer[i] = _SYMBOLS[value & 0xf]; value >>= 4; } require(value == 0, "Strings: hex length insufficient"); return string(buffer); } /** * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation. */ function toHexString(address addr) internal pure returns (string memory) { return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH); } /** * @dev Returns true if the two strings are equal. */ function equal(string memory a, string memory b) internal pure returns (bool) { return keccak256(bytes(a)) == keccak256(bytes(b)); } }
// 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); } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC5267.sol) pragma solidity ^0.8.0; interface IERC5267 { /** * @dev MAY be emitted to signal that the domain could have changed. */ event EIP712DomainChanged(); /** * @dev returns the fields and values that describe the domain separator used by this contract for EIP-712 * signature. */ function eip712Domain() external view returns ( bytes1 fields, string memory name, string memory version, uint256 chainId, address verifyingContract, bytes32 salt, uint256[] memory extensions ); }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.6.0) (proxy/Proxy.sol) pragma solidity ^0.8.0; /** * @dev This abstract contract provides a fallback function that delegates all calls to another contract using the EVM * instruction `delegatecall`. We refer to the second contract as the _implementation_ behind the proxy, and it has to * be specified by overriding the virtual {_implementation} function. * * Additionally, delegation to the implementation can be triggered manually through the {_fallback} function, or to a * different contract through the {_delegate} function. * * The success and return data of the delegated call will be returned back to the caller of the proxy. */ abstract contract Proxy { /** * @dev Delegates the current call to `implementation`. * * This function does not return to its internal call site, it will return directly to the external caller. */ function _delegate(address implementation) internal virtual { assembly { // Copy msg.data. We take full control of memory in this inline assembly // block because it will not return to Solidity code. We overwrite the // Solidity scratch pad at memory position 0. calldatacopy(0, 0, calldatasize()) // Call the implementation. // out and outsize are 0 because we don't know the size yet. let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0) // Copy the returned data. returndatacopy(0, 0, returndatasize()) switch result // delegatecall returns 0 on error. case 0 { revert(0, returndatasize()) } default { return(0, returndatasize()) } } } /** * @dev This is a virtual function that should be overridden so it returns the address to which the fallback function * and {_fallback} should delegate. */ function _implementation() internal view virtual returns (address); /** * @dev Delegates the current call to the address returned by `_implementation()`. * * This function does not return to its internal call site, it will return directly to the external caller. */ function _fallback() internal virtual { _beforeFallback(); _delegate(_implementation()); } /** * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if no other * function in the contract matches the call data. */ fallback() external payable virtual { _fallback(); } /** * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if call data * is empty. */ receive() external payable virtual { _fallback(); } /** * @dev Hook that is called before falling back to the implementation. Can happen as part of a manual `_fallback` * call, or as part of the Solidity `fallback` or `receive` functions. * * If overridden should call `super._beforeFallback()`. */ function _beforeFallback() internal virtual {} }
// 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) (token/ERC20/ERC20.sol) pragma solidity ^0.8.0; import "./IERC20.sol"; import "./extensions/IERC20Metadata.sol"; import "../../utils/Context.sol"; /** * @dev Implementation of the {IERC20} interface. * * This implementation is agnostic to the way tokens are created. This means * that a supply mechanism has to be added in a derived contract using {_mint}. * For a generic mechanism see {ERC20PresetMinterPauser}. * * TIP: For a detailed writeup see our guide * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How * to implement supply mechanisms]. * * The default value of {decimals} is 18. To change this, you should override * this function so it returns a different value. * * We have followed general OpenZeppelin Contracts guidelines: functions revert * instead returning `false` on failure. This behavior is nonetheless * conventional and does not conflict with the expectations of ERC20 * applications. * * Additionally, an {Approval} event is emitted on calls to {transferFrom}. * This allows applications to reconstruct the allowance for all accounts just * by listening to said events. Other implementations of the EIP may not emit * these events, as it isn't required by the specification. * * Finally, the non-standard {decreaseAllowance} and {increaseAllowance} * functions have been added to mitigate the well-known issues around setting * allowances. See {IERC20-approve}. */ contract ERC20 is Context, IERC20, IERC20Metadata { mapping(address => uint256) private _balances; mapping(address => mapping(address => uint256)) private _allowances; uint256 private _totalSupply; string private _name; string private _symbol; /** * @dev Sets the values for {name} and {symbol}. * * All two of these values are immutable: they can only be set once during * construction. */ constructor(string memory name_, string memory symbol_) { _name = name_; _symbol = symbol_; } /** * @dev Returns the name of the token. */ function name() public view virtual override returns (string memory) { return _name; } /** * @dev Returns the symbol of the token, usually a shorter version of the * name. */ function symbol() public view virtual override returns (string memory) { return _symbol; } /** * @dev Returns the number of decimals used to get its user representation. * For example, if `decimals` equals `2`, a balance of `505` tokens should * be displayed to a user as `5.05` (`505 / 10 ** 2`). * * Tokens usually opt for a value of 18, imitating the relationship between * Ether and Wei. This is the default value returned by this function, unless * it's overridden. * * NOTE: This information is only used for _display_ purposes: it in * no way affects any of the arithmetic of the contract, including * {IERC20-balanceOf} and {IERC20-transfer}. */ function decimals() public view virtual override returns (uint8) { return 18; } /** * @dev See {IERC20-totalSupply}. */ function totalSupply() public view virtual override returns (uint256) { return _totalSupply; } /** * @dev See {IERC20-balanceOf}. */ function balanceOf(address account) public view virtual override returns (uint256) { return _balances[account]; } /** * @dev See {IERC20-transfer}. * * Requirements: * * - `to` cannot be the zero address. * - the caller must have a balance of at least `amount`. */ function transfer(address to, uint256 amount) public virtual override returns (bool) { address owner = _msgSender(); _transfer(owner, to, amount); return true; } /** * @dev See {IERC20-allowance}. */ function allowance(address owner, address spender) public view virtual override returns (uint256) { return _allowances[owner][spender]; } /** * @dev See {IERC20-approve}. * * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on * `transferFrom`. This is semantically equivalent to an infinite approval. * * Requirements: * * - `spender` cannot be the zero address. */ function approve(address spender, uint256 amount) public virtual override returns (bool) { address owner = _msgSender(); _approve(owner, spender, amount); return true; } /** * @dev See {IERC20-transferFrom}. * * Emits an {Approval} event indicating the updated allowance. This is not * required by the EIP. See the note at the beginning of {ERC20}. * * NOTE: Does not update the allowance if the current allowance * is the maximum `uint256`. * * Requirements: * * - `from` and `to` cannot be the zero address. * - `from` must have a balance of at least `amount`. * - the caller must have allowance for ``from``'s tokens of at least * `amount`. */ function transferFrom(address from, address to, uint256 amount) public virtual override returns (bool) { address spender = _msgSender(); _spendAllowance(from, spender, amount); _transfer(from, to, amount); return true; } /** * @dev Atomically increases the allowance granted to `spender` by the caller. * * This is an alternative to {approve} that can be used as a mitigation for * problems described in {IERC20-approve}. * * Emits an {Approval} event indicating the updated allowance. * * Requirements: * * - `spender` cannot be the zero address. */ function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) { address owner = _msgSender(); _approve(owner, spender, allowance(owner, spender) + addedValue); return true; } /** * @dev Atomically decreases the allowance granted to `spender` by the caller. * * This is an alternative to {approve} that can be used as a mitigation for * problems described in {IERC20-approve}. * * Emits an {Approval} event indicating the updated allowance. * * Requirements: * * - `spender` cannot be the zero address. * - `spender` must have allowance for the caller of at least * `subtractedValue`. */ function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) { address owner = _msgSender(); uint256 currentAllowance = allowance(owner, spender); require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero"); unchecked { _approve(owner, spender, currentAllowance - subtractedValue); } return true; } /** * @dev Moves `amount` of tokens from `from` to `to`. * * This internal function is equivalent to {transfer}, and can be used to * e.g. implement automatic token fees, slashing mechanisms, etc. * * Emits a {Transfer} event. * * Requirements: * * - `from` cannot be the zero address. * - `to` cannot be the zero address. * - `from` must have a balance of at least `amount`. */ function _transfer(address from, address to, uint256 amount) internal virtual { require(from != address(0), "ERC20: transfer from the zero address"); require(to != address(0), "ERC20: transfer to the zero address"); _beforeTokenTransfer(from, to, amount); uint256 fromBalance = _balances[from]; require(fromBalance >= amount, "ERC20: transfer amount exceeds balance"); unchecked { _balances[from] = fromBalance - amount; // Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by // decrementing then incrementing. _balances[to] += amount; } emit Transfer(from, to, amount); _afterTokenTransfer(from, to, amount); } /** @dev Creates `amount` tokens and assigns them to `account`, increasing * the total supply. * * Emits a {Transfer} event with `from` set to the zero address. * * Requirements: * * - `account` cannot be the zero address. */ function _mint(address account, uint256 amount) internal virtual { require(account != address(0), "ERC20: mint to the zero address"); _beforeTokenTransfer(address(0), account, amount); _totalSupply += amount; unchecked { // Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above. _balances[account] += amount; } emit Transfer(address(0), account, amount); _afterTokenTransfer(address(0), account, amount); } /** * @dev Destroys `amount` tokens from `account`, reducing the * total supply. * * Emits a {Transfer} event with `to` set to the zero address. * * Requirements: * * - `account` cannot be the zero address. * - `account` must have at least `amount` tokens. */ function _burn(address account, uint256 amount) internal virtual { require(account != address(0), "ERC20: burn from the zero address"); _beforeTokenTransfer(account, address(0), amount); uint256 accountBalance = _balances[account]; require(accountBalance >= amount, "ERC20: burn amount exceeds balance"); unchecked { _balances[account] = accountBalance - amount; // Overflow not possible: amount <= accountBalance <= totalSupply. _totalSupply -= amount; } emit Transfer(account, address(0), amount); _afterTokenTransfer(account, address(0), amount); } /** * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens. * * This internal function is equivalent to `approve`, and can be used to * e.g. set automatic allowances for certain subsystems, etc. * * Emits an {Approval} event. * * Requirements: * * - `owner` cannot be the zero address. * - `spender` cannot be the zero address. */ function _approve(address owner, address spender, uint256 amount) internal virtual { require(owner != address(0), "ERC20: approve from the zero address"); require(spender != address(0), "ERC20: approve to the zero address"); _allowances[owner][spender] = amount; emit Approval(owner, spender, amount); } /** * @dev Updates `owner` s allowance for `spender` based on spent `amount`. * * Does not update the allowance amount in case of infinite allowance. * Revert if not enough allowance is available. * * Might emit an {Approval} event. */ function _spendAllowance(address owner, address spender, uint256 amount) internal virtual { uint256 currentAllowance = allowance(owner, spender); if (currentAllowance != type(uint256).max) { require(currentAllowance >= amount, "ERC20: insufficient allowance"); unchecked { _approve(owner, spender, currentAllowance - amount); } } } /** * @dev Hook that is called before any transfer of tokens. This includes * minting and burning. * * Calling conditions: * * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens * will be transferred to `to`. * - when `from` is zero, `amount` tokens will be minted for `to`. * - when `to` is zero, `amount` of ``from``'s tokens will be burned. * - `from` and `to` are never both zero. * * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. */ function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {} /** * @dev Hook that is called after any transfer of tokens. This includes * minting and burning. * * Calling conditions: * * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens * has been transferred to `to`. * - when `from` is zero, `amount` tokens have been minted for `to`. * - when `to` is zero, `amount` of ``from``'s tokens have been burned. * - `from` and `to` are never both zero. * * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. */ function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual {} }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/extensions/ERC20Burnable.sol) pragma solidity ^0.8.0; import "../ERC20.sol"; import "../../../utils/Context.sol"; /** * @dev Extension of {ERC20} that allows token holders to destroy both their own * tokens and those that they have an allowance for, in a way that can be * recognized off-chain (via event analysis). */ abstract contract ERC20Burnable is Context, ERC20 { /** * @dev Destroys `amount` tokens from the caller. * * See {ERC20-_burn}. */ function burn(uint256 amount) public virtual { _burn(_msgSender(), amount); } /** * @dev Destroys `amount` tokens from `account`, deducting from the caller's * allowance. * * See {ERC20-_burn} and {ERC20-allowance}. * * Requirements: * * - the caller must have allowance for ``accounts``'s tokens of at least * `amount`. */ function burnFrom(address account, uint256 amount) public virtual { _spendAllowance(account, _msgSender(), amount); _burn(account, amount); } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.4) (token/ERC20/extensions/ERC20Permit.sol) pragma solidity ^0.8.0; import "./IERC20Permit.sol"; import "../ERC20.sol"; import "../../../utils/cryptography/ECDSA.sol"; import "../../../utils/cryptography/EIP712.sol"; import "../../../utils/Counters.sol"; /** * @dev Implementation of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612]. * * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by * presenting a message signed by the account. By not relying on `{IERC20-approve}`, the token holder account doesn't * need to send a transaction, and thus is not required to hold Ether at all. * * _Available since v3.4._ */ abstract contract ERC20Permit is ERC20, IERC20Permit, EIP712 { using Counters for Counters.Counter; mapping(address => Counters.Counter) private _nonces; // solhint-disable-next-line var-name-mixedcase bytes32 private constant _PERMIT_TYPEHASH = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); /** * @dev In previous versions `_PERMIT_TYPEHASH` was declared as `immutable`. * However, to ensure consistency with the upgradeable transpiler, we will continue * to reserve a slot. * @custom:oz-renamed-from _PERMIT_TYPEHASH */ // solhint-disable-next-line var-name-mixedcase bytes32 private _PERMIT_TYPEHASH_DEPRECATED_SLOT; /** * @dev Initializes the {EIP712} domain separator using the `name` parameter, and setting `version` to `"1"`. * * It's a good idea to use the same `name` that is defined as the ERC20 token name. */ constructor(string memory name) EIP712(name, "1") {} /** * @inheritdoc IERC20Permit */ function permit( address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s ) public virtual override { require(block.timestamp <= deadline, "ERC20Permit: expired deadline"); bytes32 structHash = keccak256(abi.encode(_PERMIT_TYPEHASH, owner, spender, value, _useNonce(owner), deadline)); bytes32 hash = _hashTypedDataV4(structHash); address signer = ECDSA.recover(hash, v, r, s); require(signer == owner, "ERC20Permit: invalid signature"); _approve(owner, spender, value); } /** * @inheritdoc IERC20Permit */ function nonces(address owner) public view virtual override returns (uint256) { return _nonces[owner].current(); } /** * @inheritdoc IERC20Permit */ // solhint-disable-next-line func-name-mixedcase function DOMAIN_SEPARATOR() external view override returns (bytes32) { return _domainSeparatorV4(); } /** * @dev "Consume a nonce": return the current value and increment. * * _Available since v4.1._ */ function _useNonce(address owner) internal virtual returns (uint256 current) { Counters.Counter storage nonce = _nonces[owner]; current = nonce.current(); nonce.increment(); } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol) pragma solidity ^0.8.0; import "../IERC20.sol"; /** * @dev Interface for the optional metadata functions from the ERC20 standard. * * _Available since v4.1._ */ interface IERC20Metadata is IERC20 { /** * @dev Returns the name of the token. */ function name() external view returns (string memory); /** * @dev Returns the symbol of the token. */ function symbol() external view returns (string memory); /** * @dev Returns the decimals places of the token. */ function decimals() external view returns (uint8); }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.4) (token/ERC20/extensions/IERC20Permit.sol) pragma solidity ^0.8.0; /** * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612]. * * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't * need to send a transaction, and thus is not required to hold Ether at all. * * ==== Security Considerations * * There are two important considerations concerning the use of `permit`. The first is that a valid permit signature * expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be * considered as an intention to spend the allowance in any specific way. The second is that because permits have * built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should * take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be * generally recommended is: * * ```solidity * function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public { * try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {} * doThing(..., value); * } * * function doThing(..., uint256 value) public { * token.safeTransferFrom(msg.sender, address(this), value); * ... * } * ``` * * Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of * `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also * {SafeERC20-safeTransferFrom}). * * Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so * contracts should have entry points that don't rely on permit. */ interface IERC20Permit { /** * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens, * given ``owner``'s signed approval. * * IMPORTANT: The same issues {IERC20-approve} has related to transaction * ordering also apply here. * * Emits an {Approval} event. * * Requirements: * * - `spender` cannot be the zero address. * - `deadline` must be a timestamp in the future. * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner` * over the EIP712-formatted function arguments. * - the signature must use ``owner``'s current nonce (see {nonces}). * * For more information on the signature format, see the * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP * section]. * * CAUTION: See Security Considerations above. */ function permit( address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s ) external; /** * @dev Returns the current nonce for `owner`. This value must be * included whenever a signature is generated for {permit}. * * Every successful call to {permit} increases ``owner``'s nonce by one. This * prevents a signature from being used multiple times. */ function nonces(address owner) external view returns (uint256); /** * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}. */ // solhint-disable-next-line func-name-mixedcase function DOMAIN_SEPARATOR() external view returns (bytes32); }
// 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.3) (token/ERC20/utils/SafeERC20.sol) pragma solidity ^0.8.0; import "../IERC20.sol"; import "../extensions/IERC20Permit.sol"; import "../../../utils/Address.sol"; /** * @title SafeERC20 * @dev Wrappers around ERC20 operations that throw on failure (when the token * contract returns false). Tokens that return no value (and instead revert or * throw on failure) are also supported, non-reverting calls are assumed to be * successful. * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract, * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. */ library SafeERC20 { using Address for address; /** * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value, * non-reverting calls are assumed to be successful. */ function safeTransfer(IERC20 token, address to, uint256 value) internal { _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); } /** * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful. */ function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal { _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); } /** * @dev Deprecated. This function has issues similar to the ones found in * {IERC20-approve}, and its usage is discouraged. * * Whenever possible, use {safeIncreaseAllowance} and * {safeDecreaseAllowance} instead. */ function safeApprove(IERC20 token, address spender, uint256 value) internal { // safeApprove should only be called when setting an initial allowance, // or when resetting it to zero. To increase and decrease it, use // 'safeIncreaseAllowance' and 'safeDecreaseAllowance' require( (value == 0) || (token.allowance(address(this), spender) == 0), "SafeERC20: approve from non-zero to non-zero allowance" ); _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)); } /** * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value, * non-reverting calls are assumed to be successful. */ function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal { uint256 oldAllowance = token.allowance(address(this), spender); _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance + value)); } /** * @dev Decrease the calling contract's allowance toward `spender` by `value`. If `token` returns no value, * non-reverting calls are assumed to be successful. */ function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal { unchecked { uint256 oldAllowance = token.allowance(address(this), spender); require(oldAllowance >= value, "SafeERC20: decreased allowance below zero"); _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance - value)); } } /** * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value, * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval * to be set to zero before setting it to a non-zero value, such as USDT. */ function forceApprove(IERC20 token, address spender, uint256 value) internal { bytes memory approvalCall = abi.encodeWithSelector(token.approve.selector, spender, value); if (!_callOptionalReturnBool(token, approvalCall)) { _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, 0)); _callOptionalReturn(token, approvalCall); } } /** * @dev Use a ERC-2612 signature to set the `owner` approval toward `spender` on `token`. * Revert on invalid signature. */ function safePermit( IERC20Permit token, address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s ) internal { uint256 nonceBefore = token.nonces(owner); token.permit(owner, spender, value, deadline, v, r, s); uint256 nonceAfter = token.nonces(owner); require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed"); } /** * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement * on the return value: the return value is optional (but if data is returned, it must not be false). * @param token The token targeted by the call. * @param data The call data (encoded using abi.encode or one of its variants). */ function _callOptionalReturn(IERC20 token, bytes memory data) private { // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that // the target address contains contract code and also asserts for success in the low-level call. bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed"); require(returndata.length == 0 || abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); } /** * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement * on the return value: the return value is optional (but if data is returned, it must not be false). * @param token The token targeted by the call. * @param data The call data (encoded using abi.encode or one of its variants). * * This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead. */ function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) { // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since // we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false // and not revert is the subcall reverts. (bool success, bytes memory returndata) = address(token).call(data); return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && Address.isContract(address(token)); } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol) pragma solidity ^0.8.1; /** * @dev Collection of functions related to the address type */ library Address { /** * @dev Returns true if `account` is a contract. * * [IMPORTANT] * ==== * It is unsafe to assume that an address for which this function returns * false is an externally-owned account (EOA) and not a contract. * * Among others, `isContract` will return false for the following * types of addresses: * * - an externally-owned account * - a contract in construction * - an address where a contract will be created * - an address where a contract lived, but was destroyed * * Furthermore, `isContract` will also return true if the target contract within * the same transaction is already scheduled for destruction by `SELFDESTRUCT`, * which only has an effect at the end of a transaction. * ==== * * [IMPORTANT] * ==== * You shouldn't rely on `isContract` to protect against flash loan attacks! * * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract * constructor. * ==== */ function isContract(address account) internal view returns (bool) { // This method relies on extcodesize/address.code.length, which returns 0 // for contracts in construction, since the code is only stored at the end // of the constructor execution. return account.code.length > 0; } /** * @dev Replacement for Solidity's `transfer`: sends `amount` wei to * `recipient`, forwarding all available gas and reverting on errors. * * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost * of certain opcodes, possibly making contracts go over the 2300 gas limit * imposed by `transfer`, making them unable to receive funds via * `transfer`. {sendValue} removes this limitation. * * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more]. * * IMPORTANT: because control is transferred to `recipient`, care must be * taken to not create reentrancy vulnerabilities. Consider using * {ReentrancyGuard} or the * https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. */ function sendValue(address payable recipient, uint256 amount) internal { require(address(this).balance >= amount, "Address: insufficient balance"); (bool success, ) = recipient.call{value: amount}(""); require(success, "Address: unable to send value, recipient may have reverted"); } /** * @dev Performs a Solidity function call using a low level `call`. A * plain `call` is an unsafe replacement for a function call: use this * function instead. * * If `target` reverts with a revert reason, it is bubbled up by this * function (like regular Solidity function calls). * * Returns the raw returned data. To convert to the expected return value, * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. * * Requirements: * * - `target` must be a contract. * - calling `target` with `data` must not revert. * * _Available since v3.1._ */ function functionCall(address target, bytes memory data) internal returns (bytes memory) { return functionCallWithValue(target, data, 0, "Address: low-level call failed"); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with * `errorMessage` as a fallback revert reason when `target` reverts. * * _Available since v3.1._ */ function functionCall( address target, bytes memory data, string memory errorMessage ) internal returns (bytes memory) { return functionCallWithValue(target, data, 0, errorMessage); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but also transferring `value` wei to `target`. * * Requirements: * * - the calling contract must have an ETH balance of at least `value`. * - the called Solidity function must be `payable`. * * _Available since v3.1._ */ function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) { return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); } /** * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but * with `errorMessage` as a fallback revert reason when `target` reverts. * * _Available since v3.1._ */ function functionCallWithValue( address target, bytes memory data, uint256 value, string memory errorMessage ) internal returns (bytes memory) { require(address(this).balance >= value, "Address: insufficient balance for call"); (bool success, bytes memory returndata) = target.call{value: value}(data); return verifyCallResultFromTarget(target, success, returndata, errorMessage); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but performing a static call. * * _Available since v3.3._ */ function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { return functionStaticCall(target, data, "Address: low-level static call failed"); } /** * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], * but performing a static call. * * _Available since v3.3._ */ function functionStaticCall( address target, bytes memory data, string memory errorMessage ) internal view returns (bytes memory) { (bool success, bytes memory returndata) = target.staticcall(data); return verifyCallResultFromTarget(target, success, returndata, errorMessage); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but performing a delegate call. * * _Available since v3.4._ */ function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { return functionDelegateCall(target, data, "Address: low-level delegate call failed"); } /** * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], * but performing a delegate call. * * _Available since v3.4._ */ function functionDelegateCall( address target, bytes memory data, string memory errorMessage ) internal returns (bytes memory) { (bool success, bytes memory returndata) = target.delegatecall(data); return verifyCallResultFromTarget(target, success, returndata, errorMessage); } /** * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract. * * _Available since v4.8._ */ function verifyCallResultFromTarget( address target, bool success, bytes memory returndata, string memory errorMessage ) internal view returns (bytes memory) { if (success) { if (returndata.length == 0) { // only check isContract if the call was successful and the return data is empty // otherwise we already know that it was a contract require(isContract(target), "Address: call to non-contract"); } return returndata; } else { _revert(returndata, errorMessage); } } /** * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the * revert reason or using the provided one. * * _Available since v4.3._ */ function verifyCallResult( bool success, bytes memory returndata, string memory errorMessage ) internal pure returns (bytes memory) { if (success) { return returndata; } else { _revert(returndata, errorMessage); } } function _revert(bytes memory returndata, string memory errorMessage) private pure { // Look for revert reason and bubble it up if present if (returndata.length > 0) { // The easiest way to bubble the revert reason is using memory via assembly /// @solidity memory-safe-assembly assembly { let returndata_size := mload(returndata) revert(add(32, returndata), returndata_size) } } else { revert(errorMessage); } } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.4) (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; } function _contextSuffixLength() internal view virtual returns (uint256) { return 0; } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (utils/Counters.sol) pragma solidity ^0.8.0; /** * @title Counters * @author Matt Condon (@shrugs) * @dev Provides counters that can only be incremented, decremented or reset. This can be used e.g. to track the number * of elements in a mapping, issuing ERC721 ids, or counting request ids. * * Include with `using Counters for Counters.Counter;` */ library Counters { struct Counter { // This variable should never be directly accessed by users of the library: interactions must be restricted to // the library's function. As of Solidity v0.5.2, this cannot be enforced, though there is a proposal to add // this feature: see https://github.com/ethereum/solidity/issues/4637 uint256 _value; // default: 0 } function current(Counter storage counter) internal view returns (uint256) { return counter._value; } function increment(Counter storage counter) internal { unchecked { counter._value += 1; } } function decrement(Counter storage counter) internal { uint256 value = counter._value; require(value > 0, "Counter: decrement overflow"); unchecked { counter._value = value - 1; } } function reset(Counter storage counter) internal { counter._value = 0; } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (utils/cryptography/ECDSA.sol) pragma solidity ^0.8.0; import "../Strings.sol"; /** * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations. * * These functions can be used to verify that a message was signed by the holder * of the private keys of a given address. */ library ECDSA { enum RecoverError { NoError, InvalidSignature, InvalidSignatureLength, InvalidSignatureS, InvalidSignatureV // Deprecated in v4.8 } function _throwError(RecoverError error) private pure { if (error == RecoverError.NoError) { return; // no error: do nothing } else if (error == RecoverError.InvalidSignature) { revert("ECDSA: invalid signature"); } else if (error == RecoverError.InvalidSignatureLength) { revert("ECDSA: invalid signature length"); } else if (error == RecoverError.InvalidSignatureS) { revert("ECDSA: invalid signature 's' value"); } } /** * @dev Returns the address that signed a hashed message (`hash`) with * `signature` or error string. This address can then be used for verification purposes. * * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures: * this function rejects them by requiring the `s` value to be in the lower * half order, and the `v` value to be either 27 or 28. * * IMPORTANT: `hash` _must_ be the result of a hash operation for the * verification to be secure: it is possible to craft signatures that * recover to arbitrary addresses for non-hashed data. A safe way to ensure * this is by receiving a hash of the original message (which may otherwise * be too long), and then calling {toEthSignedMessageHash} on it. * * Documentation for signature generation: * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js] * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers] * * _Available since v4.3._ */ function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) { if (signature.length == 65) { bytes32 r; bytes32 s; uint8 v; // ecrecover takes the signature parameters, and the only way to get them // currently is to use assembly. /// @solidity memory-safe-assembly assembly { r := mload(add(signature, 0x20)) s := mload(add(signature, 0x40)) v := byte(0, mload(add(signature, 0x60))) } return tryRecover(hash, v, r, s); } else { return (address(0), RecoverError.InvalidSignatureLength); } } /** * @dev Returns the address that signed a hashed message (`hash`) with * `signature`. This address can then be used for verification purposes. * * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures: * this function rejects them by requiring the `s` value to be in the lower * half order, and the `v` value to be either 27 or 28. * * IMPORTANT: `hash` _must_ be the result of a hash operation for the * verification to be secure: it is possible to craft signatures that * recover to arbitrary addresses for non-hashed data. A safe way to ensure * this is by receiving a hash of the original message (which may otherwise * be too long), and then calling {toEthSignedMessageHash} on it. */ function recover(bytes32 hash, bytes memory signature) internal pure returns (address) { (address recovered, RecoverError error) = tryRecover(hash, signature); _throwError(error); return recovered; } /** * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately. * * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures] * * _Available since v4.3._ */ function tryRecover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address, RecoverError) { bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff); uint8 v = uint8((uint256(vs) >> 255) + 27); return tryRecover(hash, v, r, s); } /** * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately. * * _Available since v4.2._ */ function recover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address) { (address recovered, RecoverError error) = tryRecover(hash, r, vs); _throwError(error); return recovered; } /** * @dev Overload of {ECDSA-tryRecover} that receives the `v`, * `r` and `s` signature fields separately. * * _Available since v4.3._ */ function tryRecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address, RecoverError) { // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most // signatures from current libraries generate a unique signature with an s-value in the lower half order. // // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept // these malleable signatures as well. if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) { return (address(0), RecoverError.InvalidSignatureS); } // If the signature is valid (and not malleable), return the signer address address signer = ecrecover(hash, v, r, s); if (signer == address(0)) { return (address(0), RecoverError.InvalidSignature); } return (signer, RecoverError.NoError); } /** * @dev Overload of {ECDSA-recover} that receives the `v`, * `r` and `s` signature fields separately. */ function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) { (address recovered, RecoverError error) = tryRecover(hash, v, r, s); _throwError(error); return recovered; } /** * @dev Returns an Ethereum Signed Message, created from a `hash`. This * produces hash corresponding to the one signed with the * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`] * JSON-RPC method as part of EIP-191. * * See {recover}. */ function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32 message) { // 32 is the length in bytes of hash, // enforced by the type signature above /// @solidity memory-safe-assembly assembly { mstore(0x00, "\x19Ethereum Signed Message:\n32") mstore(0x1c, hash) message := keccak256(0x00, 0x3c) } } /** * @dev Returns an Ethereum Signed Message, created from `s`. This * produces hash corresponding to the one signed with the * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`] * JSON-RPC method as part of EIP-191. * * See {recover}. */ function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32) { return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n", Strings.toString(s.length), s)); } /** * @dev Returns an Ethereum Signed Typed Data, created from a * `domainSeparator` and a `structHash`. This produces hash corresponding * to the one signed with the * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`] * JSON-RPC method as part of EIP-712. * * See {recover}. */ function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32 data) { /// @solidity memory-safe-assembly assembly { let ptr := mload(0x40) mstore(ptr, "\x19\x01") mstore(add(ptr, 0x02), domainSeparator) mstore(add(ptr, 0x22), structHash) data := keccak256(ptr, 0x42) } } /** * @dev Returns an Ethereum Signed Data with intended validator, created from a * `validator` and `data` according to the version 0 of EIP-191. * * See {recover}. */ function toDataWithIntendedValidatorHash(address validator, bytes memory data) internal pure returns (bytes32) { return keccak256(abi.encodePacked("\x19\x00", validator, data)); } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (utils/cryptography/EIP712.sol) pragma solidity ^0.8.8; import "./ECDSA.sol"; import "../ShortStrings.sol"; import "../../interfaces/IERC5267.sol"; /** * @dev https://eips.ethereum.org/EIPS/eip-712[EIP 712] is a standard for hashing and signing of typed structured data. * * The encoding specified in the EIP is very generic, and such a generic implementation in Solidity is not feasible, * thus this contract does not implement the encoding itself. Protocols need to implement the type-specific encoding * they need in their contracts using a combination of `abi.encode` and `keccak256`. * * This contract implements the EIP 712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding * scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA * ({_hashTypedDataV4}). * * The implementation of the domain separator was designed to be as efficient as possible while still properly updating * the chain id to protect against replay attacks on an eventual fork of the chain. * * NOTE: This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method * https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask]. * * NOTE: In the upgradeable version of this contract, the cached values will correspond to the address, and the domain * separator of the implementation contract. This will cause the `_domainSeparatorV4` function to always rebuild the * separator from the immutable values, which is cheaper than accessing a cached version in cold storage. * * _Available since v3.4._ * * @custom:oz-upgrades-unsafe-allow state-variable-immutable state-variable-assignment */ abstract contract EIP712 is IERC5267 { using ShortStrings for *; bytes32 private constant _TYPE_HASH = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); // Cache the domain separator as an immutable value, but also store the chain id that it corresponds to, in order to // invalidate the cached domain separator if the chain id changes. bytes32 private immutable _cachedDomainSeparator; uint256 private immutable _cachedChainId; address private immutable _cachedThis; bytes32 private immutable _hashedName; bytes32 private immutable _hashedVersion; ShortString private immutable _name; ShortString private immutable _version; string private _nameFallback; string private _versionFallback; /** * @dev Initializes the domain separator and parameter caches. * * The meaning of `name` and `version` is specified in * https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP 712]: * * - `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol. * - `version`: the current major version of the signing domain. * * NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart * contract upgrade]. */ constructor(string memory name, string memory version) { _name = name.toShortStringWithFallback(_nameFallback); _version = version.toShortStringWithFallback(_versionFallback); _hashedName = keccak256(bytes(name)); _hashedVersion = keccak256(bytes(version)); _cachedChainId = block.chainid; _cachedDomainSeparator = _buildDomainSeparator(); _cachedThis = address(this); } /** * @dev Returns the domain separator for the current chain. */ function _domainSeparatorV4() internal view returns (bytes32) { if (address(this) == _cachedThis && block.chainid == _cachedChainId) { return _cachedDomainSeparator; } else { return _buildDomainSeparator(); } } function _buildDomainSeparator() private view returns (bytes32) { return keccak256(abi.encode(_TYPE_HASH, _hashedName, _hashedVersion, block.chainid, address(this))); } /** * @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this * function returns the hash of the fully encoded EIP712 message for this domain. * * This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example: * * ```solidity * bytes32 digest = _hashTypedDataV4(keccak256(abi.encode( * keccak256("Mail(address to,string contents)"), * mailTo, * keccak256(bytes(mailContents)) * ))); * address signer = ECDSA.recover(digest, signature); * ``` */ function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) { return ECDSA.toTypedDataHash(_domainSeparatorV4(), structHash); } /** * @dev See {EIP-5267}. * * _Available since v4.9._ */ function eip712Domain() public view virtual override returns ( bytes1 fields, string memory name, string memory version, uint256 chainId, address verifyingContract, bytes32 salt, uint256[] memory extensions ) { return ( hex"0f", // 01111 _name.toStringWithFallback(_nameFallback), _version.toStringWithFallback(_versionFallback), block.chainid, address(this), bytes32(0), new uint256[](0) ); } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol) pragma solidity ^0.8.0; /** * @dev Interface of the ERC165 standard, as defined in the * https://eips.ethereum.org/EIPS/eip-165[EIP]. * * Implementers can declare support of contract interfaces, which can then be * queried by others ({ERC165Checker}). * * For an implementation, see {ERC165}. */ interface IERC165 { /** * @dev Returns true if this contract implements the interface defined by * `interfaceId`. See the corresponding * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] * to learn more about how these ids are created. * * This function call must use less than 30 000 gas. */ function supportsInterface(bytes4 interfaceId) external view returns (bool); }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (utils/math/Math.sol) pragma solidity ^0.8.0; /** * @dev Standard math utilities missing in the Solidity language. */ library Math { enum Rounding { Down, // Toward negative infinity Up, // Toward infinity Zero // Toward zero } /** * @dev Returns the largest of two numbers. */ function max(uint256 a, uint256 b) internal pure returns (uint256) { return a > b ? a : b; } /** * @dev Returns the smallest of two numbers. */ function min(uint256 a, uint256 b) internal pure returns (uint256) { return a < b ? a : b; } /** * @dev Returns the average of two numbers. The result is rounded towards * zero. */ function average(uint256 a, uint256 b) internal pure returns (uint256) { // (a + b) / 2 can overflow. return (a & b) + (a ^ b) / 2; } /** * @dev Returns the ceiling of the division of two numbers. * * This differs from standard division with `/` in that it rounds up instead * of rounding down. */ function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) { // (a + b - 1) / b can overflow on addition, so we distribute. return a == 0 ? 0 : (a - 1) / b + 1; } /** * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0 * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) * with further edits by Uniswap Labs also under MIT license. */ function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) { unchecked { // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256 // variables such that product = prod1 * 2^256 + prod0. uint256 prod0; // Least significant 256 bits of the product uint256 prod1; // Most significant 256 bits of the product assembly { let mm := mulmod(x, y, not(0)) prod0 := mul(x, y) prod1 := sub(sub(mm, prod0), lt(mm, prod0)) } // Handle non-overflow cases, 256 by 256 division. if (prod1 == 0) { // Solidity will revert if denominator == 0, unlike the div opcode on its own. // The surrounding unchecked block does not change this fact. // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic. return prod0 / denominator; } // Make sure the result is less than 2^256. Also prevents denominator == 0. require(denominator > prod1, "Math: mulDiv overflow"); /////////////////////////////////////////////// // 512 by 256 division. /////////////////////////////////////////////// // Make division exact by subtracting the remainder from [prod1 prod0]. uint256 remainder; assembly { // Compute remainder using mulmod. remainder := mulmod(x, y, denominator) // Subtract 256 bit number from 512 bit number. prod1 := sub(prod1, gt(remainder, prod0)) prod0 := sub(prod0, remainder) } // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1. // See https://cs.stackexchange.com/q/138556/92363. // Does not overflow because the denominator cannot be zero at this stage in the function. uint256 twos = denominator & (~denominator + 1); assembly { // Divide denominator by twos. denominator := div(denominator, twos) // Divide [prod1 prod0] by twos. prod0 := div(prod0, twos) // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one. twos := add(div(sub(0, twos), twos), 1) } // Shift in bits from prod1 into prod0. prod0 |= prod1 * twos; // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for // four bits. That is, denominator * inv = 1 mod 2^4. uint256 inverse = (3 * denominator) ^ 2; // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works // in modular arithmetic, doubling the correct bits in each step. inverse *= 2 - denominator * inverse; // inverse mod 2^8 inverse *= 2 - denominator * inverse; // inverse mod 2^16 inverse *= 2 - denominator * inverse; // inverse mod 2^32 inverse *= 2 - denominator * inverse; // inverse mod 2^64 inverse *= 2 - denominator * inverse; // inverse mod 2^128 inverse *= 2 - denominator * inverse; // inverse mod 2^256 // Because the division is now exact we can divide by multiplying with the modular inverse of denominator. // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1 // is no longer required. result = prod0 * inverse; return result; } } /** * @notice Calculates x * y / denominator with full precision, following the selected rounding direction. */ function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) { uint256 result = mulDiv(x, y, denominator); if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) { result += 1; } return result; } /** * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down. * * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11). */ function sqrt(uint256 a) internal pure returns (uint256) { if (a == 0) { return 0; } // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target. // // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`. // // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)` // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))` // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)` // // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit. uint256 result = 1 << (log2(a) >> 1); // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128, // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision // into the expected uint128 result. unchecked { result = (result + a / result) >> 1; result = (result + a / result) >> 1; result = (result + a / result) >> 1; result = (result + a / result) >> 1; result = (result + a / result) >> 1; result = (result + a / result) >> 1; result = (result + a / result) >> 1; return min(result, a / result); } } /** * @notice Calculates sqrt(a), following the selected rounding direction. */ function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) { unchecked { uint256 result = sqrt(a); return result + (rounding == Rounding.Up && result * result < a ? 1 : 0); } } /** * @dev Return the log in base 2, rounded down, of a positive value. * Returns 0 if given 0. */ function log2(uint256 value) internal pure returns (uint256) { uint256 result = 0; unchecked { if (value >> 128 > 0) { value >>= 128; result += 128; } if (value >> 64 > 0) { value >>= 64; result += 64; } if (value >> 32 > 0) { value >>= 32; result += 32; } if (value >> 16 > 0) { value >>= 16; result += 16; } if (value >> 8 > 0) { value >>= 8; result += 8; } if (value >> 4 > 0) { value >>= 4; result += 4; } if (value >> 2 > 0) { value >>= 2; result += 2; } if (value >> 1 > 0) { result += 1; } } return result; } /** * @dev Return the log in base 2, following the selected rounding direction, of a positive value. * Returns 0 if given 0. */ function log2(uint256 value, Rounding rounding) internal pure returns (uint256) { unchecked { uint256 result = log2(value); return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0); } } /** * @dev Return the log in base 10, rounded down, of a positive value. * Returns 0 if given 0. */ function log10(uint256 value) internal pure returns (uint256) { uint256 result = 0; unchecked { if (value >= 10 ** 64) { value /= 10 ** 64; result += 64; } if (value >= 10 ** 32) { value /= 10 ** 32; result += 32; } if (value >= 10 ** 16) { value /= 10 ** 16; result += 16; } if (value >= 10 ** 8) { value /= 10 ** 8; result += 8; } if (value >= 10 ** 4) { value /= 10 ** 4; result += 4; } if (value >= 10 ** 2) { value /= 10 ** 2; result += 2; } if (value >= 10 ** 1) { result += 1; } } return result; } /** * @dev Return the log in base 10, following the selected rounding direction, of a positive value. * Returns 0 if given 0. */ function log10(uint256 value, Rounding rounding) internal pure returns (uint256) { unchecked { uint256 result = log10(value); return result + (rounding == Rounding.Up && 10 ** result < value ? 1 : 0); } } /** * @dev Return the log in base 256, rounded down, of a positive value. * Returns 0 if given 0. * * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string. */ function log256(uint256 value) internal pure returns (uint256) { uint256 result = 0; unchecked { if (value >> 128 > 0) { value >>= 128; result += 16; } if (value >> 64 > 0) { value >>= 64; result += 8; } if (value >> 32 > 0) { value >>= 32; result += 4; } if (value >> 16 > 0) { value >>= 16; result += 2; } if (value >> 8 > 0) { result += 1; } } return result; } /** * @dev Return the log in base 256, following the selected rounding direction, of a positive value. * Returns 0 if given 0. */ function log256(uint256 value, Rounding rounding) internal pure returns (uint256) { unchecked { uint256 result = log256(value); return result + (rounding == Rounding.Up && 1 << (result << 3) < value ? 1 : 0); } } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.8.0) (utils/math/SafeCast.sol) // This file was procedurally generated from scripts/generate/templates/SafeCast.js. pragma solidity ^0.8.0; /** * @dev Wrappers over Solidity's uintXX/intXX casting operators with added overflow * checks. * * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can * easily result in undesired exploitation or bugs, since developers usually * assume that overflows raise errors. `SafeCast` restores this intuition by * reverting the transaction when such an operation overflows. * * Using this library instead of the unchecked operations eliminates an entire * class of bugs, so it's recommended to use it always. * * Can be combined with {SafeMath} and {SignedSafeMath} to extend it to smaller types, by performing * all math on `uint256` and `int256` and then downcasting. */ library SafeCast { /** * @dev Returns the downcasted uint248 from uint256, reverting on * overflow (when the input is greater than largest uint248). * * Counterpart to Solidity's `uint248` operator. * * Requirements: * * - input must fit into 248 bits * * _Available since v4.7._ */ function toUint248(uint256 value) internal pure returns (uint248) { require(value <= type(uint248).max, "SafeCast: value doesn't fit in 248 bits"); return uint248(value); } /** * @dev Returns the downcasted uint240 from uint256, reverting on * overflow (when the input is greater than largest uint240). * * Counterpart to Solidity's `uint240` operator. * * Requirements: * * - input must fit into 240 bits * * _Available since v4.7._ */ function toUint240(uint256 value) internal pure returns (uint240) { require(value <= type(uint240).max, "SafeCast: value doesn't fit in 240 bits"); return uint240(value); } /** * @dev Returns the downcasted uint232 from uint256, reverting on * overflow (when the input is greater than largest uint232). * * Counterpart to Solidity's `uint232` operator. * * Requirements: * * - input must fit into 232 bits * * _Available since v4.7._ */ function toUint232(uint256 value) internal pure returns (uint232) { require(value <= type(uint232).max, "SafeCast: value doesn't fit in 232 bits"); return uint232(value); } /** * @dev Returns the downcasted uint224 from uint256, reverting on * overflow (when the input is greater than largest uint224). * * Counterpart to Solidity's `uint224` operator. * * Requirements: * * - input must fit into 224 bits * * _Available since v4.2._ */ function toUint224(uint256 value) internal pure returns (uint224) { require(value <= type(uint224).max, "SafeCast: value doesn't fit in 224 bits"); return uint224(value); } /** * @dev Returns the downcasted uint216 from uint256, reverting on * overflow (when the input is greater than largest uint216). * * Counterpart to Solidity's `uint216` operator. * * Requirements: * * - input must fit into 216 bits * * _Available since v4.7._ */ function toUint216(uint256 value) internal pure returns (uint216) { require(value <= type(uint216).max, "SafeCast: value doesn't fit in 216 bits"); return uint216(value); } /** * @dev Returns the downcasted uint208 from uint256, reverting on * overflow (when the input is greater than largest uint208). * * Counterpart to Solidity's `uint208` operator. * * Requirements: * * - input must fit into 208 bits * * _Available since v4.7._ */ function toUint208(uint256 value) internal pure returns (uint208) { require(value <= type(uint208).max, "SafeCast: value doesn't fit in 208 bits"); return uint208(value); } /** * @dev Returns the downcasted uint200 from uint256, reverting on * overflow (when the input is greater than largest uint200). * * Counterpart to Solidity's `uint200` operator. * * Requirements: * * - input must fit into 200 bits * * _Available since v4.7._ */ function toUint200(uint256 value) internal pure returns (uint200) { require(value <= type(uint200).max, "SafeCast: value doesn't fit in 200 bits"); return uint200(value); } /** * @dev Returns the downcasted uint192 from uint256, reverting on * overflow (when the input is greater than largest uint192). * * Counterpart to Solidity's `uint192` operator. * * Requirements: * * - input must fit into 192 bits * * _Available since v4.7._ */ function toUint192(uint256 value) internal pure returns (uint192) { require(value <= type(uint192).max, "SafeCast: value doesn't fit in 192 bits"); return uint192(value); } /** * @dev Returns the downcasted uint184 from uint256, reverting on * overflow (when the input is greater than largest uint184). * * Counterpart to Solidity's `uint184` operator. * * Requirements: * * - input must fit into 184 bits * * _Available since v4.7._ */ function toUint184(uint256 value) internal pure returns (uint184) { require(value <= type(uint184).max, "SafeCast: value doesn't fit in 184 bits"); return uint184(value); } /** * @dev Returns the downcasted uint176 from uint256, reverting on * overflow (when the input is greater than largest uint176). * * Counterpart to Solidity's `uint176` operator. * * Requirements: * * - input must fit into 176 bits * * _Available since v4.7._ */ function toUint176(uint256 value) internal pure returns (uint176) { require(value <= type(uint176).max, "SafeCast: value doesn't fit in 176 bits"); return uint176(value); } /** * @dev Returns the downcasted uint168 from uint256, reverting on * overflow (when the input is greater than largest uint168). * * Counterpart to Solidity's `uint168` operator. * * Requirements: * * - input must fit into 168 bits * * _Available since v4.7._ */ function toUint168(uint256 value) internal pure returns (uint168) { require(value <= type(uint168).max, "SafeCast: value doesn't fit in 168 bits"); return uint168(value); } /** * @dev Returns the downcasted uint160 from uint256, reverting on * overflow (when the input is greater than largest uint160). * * Counterpart to Solidity's `uint160` operator. * * Requirements: * * - input must fit into 160 bits * * _Available since v4.7._ */ function toUint160(uint256 value) internal pure returns (uint160) { require(value <= type(uint160).max, "SafeCast: value doesn't fit in 160 bits"); return uint160(value); } /** * @dev Returns the downcasted uint152 from uint256, reverting on * overflow (when the input is greater than largest uint152). * * Counterpart to Solidity's `uint152` operator. * * Requirements: * * - input must fit into 152 bits * * _Available since v4.7._ */ function toUint152(uint256 value) internal pure returns (uint152) { require(value <= type(uint152).max, "SafeCast: value doesn't fit in 152 bits"); return uint152(value); } /** * @dev Returns the downcasted uint144 from uint256, reverting on * overflow (when the input is greater than largest uint144). * * Counterpart to Solidity's `uint144` operator. * * Requirements: * * - input must fit into 144 bits * * _Available since v4.7._ */ function toUint144(uint256 value) internal pure returns (uint144) { require(value <= type(uint144).max, "SafeCast: value doesn't fit in 144 bits"); return uint144(value); } /** * @dev Returns the downcasted uint136 from uint256, reverting on * overflow (when the input is greater than largest uint136). * * Counterpart to Solidity's `uint136` operator. * * Requirements: * * - input must fit into 136 bits * * _Available since v4.7._ */ function toUint136(uint256 value) internal pure returns (uint136) { require(value <= type(uint136).max, "SafeCast: value doesn't fit in 136 bits"); return uint136(value); } /** * @dev Returns the downcasted uint128 from uint256, reverting on * overflow (when the input is greater than largest uint128). * * Counterpart to Solidity's `uint128` operator. * * Requirements: * * - input must fit into 128 bits * * _Available since v2.5._ */ function toUint128(uint256 value) internal pure returns (uint128) { require(value <= type(uint128).max, "SafeCast: value doesn't fit in 128 bits"); return uint128(value); } /** * @dev Returns the downcasted uint120 from uint256, reverting on * overflow (when the input is greater than largest uint120). * * Counterpart to Solidity's `uint120` operator. * * Requirements: * * - input must fit into 120 bits * * _Available since v4.7._ */ function toUint120(uint256 value) internal pure returns (uint120) { require(value <= type(uint120).max, "SafeCast: value doesn't fit in 120 bits"); return uint120(value); } /** * @dev Returns the downcasted uint112 from uint256, reverting on * overflow (when the input is greater than largest uint112). * * Counterpart to Solidity's `uint112` operator. * * Requirements: * * - input must fit into 112 bits * * _Available since v4.7._ */ function toUint112(uint256 value) internal pure returns (uint112) { require(value <= type(uint112).max, "SafeCast: value doesn't fit in 112 bits"); return uint112(value); } /** * @dev Returns the downcasted uint104 from uint256, reverting on * overflow (when the input is greater than largest uint104). * * Counterpart to Solidity's `uint104` operator. * * Requirements: * * - input must fit into 104 bits * * _Available since v4.7._ */ function toUint104(uint256 value) internal pure returns (uint104) { require(value <= type(uint104).max, "SafeCast: value doesn't fit in 104 bits"); return uint104(value); } /** * @dev Returns the downcasted uint96 from uint256, reverting on * overflow (when the input is greater than largest uint96). * * Counterpart to Solidity's `uint96` operator. * * Requirements: * * - input must fit into 96 bits * * _Available since v4.2._ */ function toUint96(uint256 value) internal pure returns (uint96) { require(value <= type(uint96).max, "SafeCast: value doesn't fit in 96 bits"); return uint96(value); } /** * @dev Returns the downcasted uint88 from uint256, reverting on * overflow (when the input is greater than largest uint88). * * Counterpart to Solidity's `uint88` operator. * * Requirements: * * - input must fit into 88 bits * * _Available since v4.7._ */ function toUint88(uint256 value) internal pure returns (uint88) { require(value <= type(uint88).max, "SafeCast: value doesn't fit in 88 bits"); return uint88(value); } /** * @dev Returns the downcasted uint80 from uint256, reverting on * overflow (when the input is greater than largest uint80). * * Counterpart to Solidity's `uint80` operator. * * Requirements: * * - input must fit into 80 bits * * _Available since v4.7._ */ function toUint80(uint256 value) internal pure returns (uint80) { require(value <= type(uint80).max, "SafeCast: value doesn't fit in 80 bits"); return uint80(value); } /** * @dev Returns the downcasted uint72 from uint256, reverting on * overflow (when the input is greater than largest uint72). * * Counterpart to Solidity's `uint72` operator. * * Requirements: * * - input must fit into 72 bits * * _Available since v4.7._ */ function toUint72(uint256 value) internal pure returns (uint72) { require(value <= type(uint72).max, "SafeCast: value doesn't fit in 72 bits"); return uint72(value); } /** * @dev Returns the downcasted uint64 from uint256, reverting on * overflow (when the input is greater than largest uint64). * * Counterpart to Solidity's `uint64` operator. * * Requirements: * * - input must fit into 64 bits * * _Available since v2.5._ */ function toUint64(uint256 value) internal pure returns (uint64) { require(value <= type(uint64).max, "SafeCast: value doesn't fit in 64 bits"); return uint64(value); } /** * @dev Returns the downcasted uint56 from uint256, reverting on * overflow (when the input is greater than largest uint56). * * Counterpart to Solidity's `uint56` operator. * * Requirements: * * - input must fit into 56 bits * * _Available since v4.7._ */ function toUint56(uint256 value) internal pure returns (uint56) { require(value <= type(uint56).max, "SafeCast: value doesn't fit in 56 bits"); return uint56(value); } /** * @dev Returns the downcasted uint48 from uint256, reverting on * overflow (when the input is greater than largest uint48). * * Counterpart to Solidity's `uint48` operator. * * Requirements: * * - input must fit into 48 bits * * _Available since v4.7._ */ function toUint48(uint256 value) internal pure returns (uint48) { require(value <= type(uint48).max, "SafeCast: value doesn't fit in 48 bits"); return uint48(value); } /** * @dev Returns the downcasted uint40 from uint256, reverting on * overflow (when the input is greater than largest uint40). * * Counterpart to Solidity's `uint40` operator. * * Requirements: * * - input must fit into 40 bits * * _Available since v4.7._ */ function toUint40(uint256 value) internal pure returns (uint40) { require(value <= type(uint40).max, "SafeCast: value doesn't fit in 40 bits"); return uint40(value); } /** * @dev Returns the downcasted uint32 from uint256, reverting on * overflow (when the input is greater than largest uint32). * * Counterpart to Solidity's `uint32` operator. * * Requirements: * * - input must fit into 32 bits * * _Available since v2.5._ */ function toUint32(uint256 value) internal pure returns (uint32) { require(value <= type(uint32).max, "SafeCast: value doesn't fit in 32 bits"); return uint32(value); } /** * @dev Returns the downcasted uint24 from uint256, reverting on * overflow (when the input is greater than largest uint24). * * Counterpart to Solidity's `uint24` operator. * * Requirements: * * - input must fit into 24 bits * * _Available since v4.7._ */ function toUint24(uint256 value) internal pure returns (uint24) { require(value <= type(uint24).max, "SafeCast: value doesn't fit in 24 bits"); return uint24(value); } /** * @dev Returns the downcasted uint16 from uint256, reverting on * overflow (when the input is greater than largest uint16). * * Counterpart to Solidity's `uint16` operator. * * Requirements: * * - input must fit into 16 bits * * _Available since v2.5._ */ function toUint16(uint256 value) internal pure returns (uint16) { require(value <= type(uint16).max, "SafeCast: value doesn't fit in 16 bits"); return uint16(value); } /** * @dev Returns the downcasted uint8 from uint256, reverting on * overflow (when the input is greater than largest uint8). * * Counterpart to Solidity's `uint8` operator. * * Requirements: * * - input must fit into 8 bits * * _Available since v2.5._ */ function toUint8(uint256 value) internal pure returns (uint8) { require(value <= type(uint8).max, "SafeCast: value doesn't fit in 8 bits"); return uint8(value); } /** * @dev Converts a signed int256 into an unsigned uint256. * * Requirements: * * - input must be greater than or equal to 0. * * _Available since v3.0._ */ function toUint256(int256 value) internal pure returns (uint256) { require(value >= 0, "SafeCast: value must be positive"); return uint256(value); } /** * @dev Returns the downcasted int248 from int256, reverting on * overflow (when the input is less than smallest int248 or * greater than largest int248). * * Counterpart to Solidity's `int248` operator. * * Requirements: * * - input must fit into 248 bits * * _Available since v4.7._ */ function toInt248(int256 value) internal pure returns (int248 downcasted) { downcasted = int248(value); require(downcasted == value, "SafeCast: value doesn't fit in 248 bits"); } /** * @dev Returns the downcasted int240 from int256, reverting on * overflow (when the input is less than smallest int240 or * greater than largest int240). * * Counterpart to Solidity's `int240` operator. * * Requirements: * * - input must fit into 240 bits * * _Available since v4.7._ */ function toInt240(int256 value) internal pure returns (int240 downcasted) { downcasted = int240(value); require(downcasted == value, "SafeCast: value doesn't fit in 240 bits"); } /** * @dev Returns the downcasted int232 from int256, reverting on * overflow (when the input is less than smallest int232 or * greater than largest int232). * * Counterpart to Solidity's `int232` operator. * * Requirements: * * - input must fit into 232 bits * * _Available since v4.7._ */ function toInt232(int256 value) internal pure returns (int232 downcasted) { downcasted = int232(value); require(downcasted == value, "SafeCast: value doesn't fit in 232 bits"); } /** * @dev Returns the downcasted int224 from int256, reverting on * overflow (when the input is less than smallest int224 or * greater than largest int224). * * Counterpart to Solidity's `int224` operator. * * Requirements: * * - input must fit into 224 bits * * _Available since v4.7._ */ function toInt224(int256 value) internal pure returns (int224 downcasted) { downcasted = int224(value); require(downcasted == value, "SafeCast: value doesn't fit in 224 bits"); } /** * @dev Returns the downcasted int216 from int256, reverting on * overflow (when the input is less than smallest int216 or * greater than largest int216). * * Counterpart to Solidity's `int216` operator. * * Requirements: * * - input must fit into 216 bits * * _Available since v4.7._ */ function toInt216(int256 value) internal pure returns (int216 downcasted) { downcasted = int216(value); require(downcasted == value, "SafeCast: value doesn't fit in 216 bits"); } /** * @dev Returns the downcasted int208 from int256, reverting on * overflow (when the input is less than smallest int208 or * greater than largest int208). * * Counterpart to Solidity's `int208` operator. * * Requirements: * * - input must fit into 208 bits * * _Available since v4.7._ */ function toInt208(int256 value) internal pure returns (int208 downcasted) { downcasted = int208(value); require(downcasted == value, "SafeCast: value doesn't fit in 208 bits"); } /** * @dev Returns the downcasted int200 from int256, reverting on * overflow (when the input is less than smallest int200 or * greater than largest int200). * * Counterpart to Solidity's `int200` operator. * * Requirements: * * - input must fit into 200 bits * * _Available since v4.7._ */ function toInt200(int256 value) internal pure returns (int200 downcasted) { downcasted = int200(value); require(downcasted == value, "SafeCast: value doesn't fit in 200 bits"); } /** * @dev Returns the downcasted int192 from int256, reverting on * overflow (when the input is less than smallest int192 or * greater than largest int192). * * Counterpart to Solidity's `int192` operator. * * Requirements: * * - input must fit into 192 bits * * _Available since v4.7._ */ function toInt192(int256 value) internal pure returns (int192 downcasted) { downcasted = int192(value); require(downcasted == value, "SafeCast: value doesn't fit in 192 bits"); } /** * @dev Returns the downcasted int184 from int256, reverting on * overflow (when the input is less than smallest int184 or * greater than largest int184). * * Counterpart to Solidity's `int184` operator. * * Requirements: * * - input must fit into 184 bits * * _Available since v4.7._ */ function toInt184(int256 value) internal pure returns (int184 downcasted) { downcasted = int184(value); require(downcasted == value, "SafeCast: value doesn't fit in 184 bits"); } /** * @dev Returns the downcasted int176 from int256, reverting on * overflow (when the input is less than smallest int176 or * greater than largest int176). * * Counterpart to Solidity's `int176` operator. * * Requirements: * * - input must fit into 176 bits * * _Available since v4.7._ */ function toInt176(int256 value) internal pure returns (int176 downcasted) { downcasted = int176(value); require(downcasted == value, "SafeCast: value doesn't fit in 176 bits"); } /** * @dev Returns the downcasted int168 from int256, reverting on * overflow (when the input is less than smallest int168 or * greater than largest int168). * * Counterpart to Solidity's `int168` operator. * * Requirements: * * - input must fit into 168 bits * * _Available since v4.7._ */ function toInt168(int256 value) internal pure returns (int168 downcasted) { downcasted = int168(value); require(downcasted == value, "SafeCast: value doesn't fit in 168 bits"); } /** * @dev Returns the downcasted int160 from int256, reverting on * overflow (when the input is less than smallest int160 or * greater than largest int160). * * Counterpart to Solidity's `int160` operator. * * Requirements: * * - input must fit into 160 bits * * _Available since v4.7._ */ function toInt160(int256 value) internal pure returns (int160 downcasted) { downcasted = int160(value); require(downcasted == value, "SafeCast: value doesn't fit in 160 bits"); } /** * @dev Returns the downcasted int152 from int256, reverting on * overflow (when the input is less than smallest int152 or * greater than largest int152). * * Counterpart to Solidity's `int152` operator. * * Requirements: * * - input must fit into 152 bits * * _Available since v4.7._ */ function toInt152(int256 value) internal pure returns (int152 downcasted) { downcasted = int152(value); require(downcasted == value, "SafeCast: value doesn't fit in 152 bits"); } /** * @dev Returns the downcasted int144 from int256, reverting on * overflow (when the input is less than smallest int144 or * greater than largest int144). * * Counterpart to Solidity's `int144` operator. * * Requirements: * * - input must fit into 144 bits * * _Available since v4.7._ */ function toInt144(int256 value) internal pure returns (int144 downcasted) { downcasted = int144(value); require(downcasted == value, "SafeCast: value doesn't fit in 144 bits"); } /** * @dev Returns the downcasted int136 from int256, reverting on * overflow (when the input is less than smallest int136 or * greater than largest int136). * * Counterpart to Solidity's `int136` operator. * * Requirements: * * - input must fit into 136 bits * * _Available since v4.7._ */ function toInt136(int256 value) internal pure returns (int136 downcasted) { downcasted = int136(value); require(downcasted == value, "SafeCast: value doesn't fit in 136 bits"); } /** * @dev Returns the downcasted int128 from int256, reverting on * overflow (when the input is less than smallest int128 or * greater than largest int128). * * Counterpart to Solidity's `int128` operator. * * Requirements: * * - input must fit into 128 bits * * _Available since v3.1._ */ function toInt128(int256 value) internal pure returns (int128 downcasted) { downcasted = int128(value); require(downcasted == value, "SafeCast: value doesn't fit in 128 bits"); } /** * @dev Returns the downcasted int120 from int256, reverting on * overflow (when the input is less than smallest int120 or * greater than largest int120). * * Counterpart to Solidity's `int120` operator. * * Requirements: * * - input must fit into 120 bits * * _Available since v4.7._ */ function toInt120(int256 value) internal pure returns (int120 downcasted) { downcasted = int120(value); require(downcasted == value, "SafeCast: value doesn't fit in 120 bits"); } /** * @dev Returns the downcasted int112 from int256, reverting on * overflow (when the input is less than smallest int112 or * greater than largest int112). * * Counterpart to Solidity's `int112` operator. * * Requirements: * * - input must fit into 112 bits * * _Available since v4.7._ */ function toInt112(int256 value) internal pure returns (int112 downcasted) { downcasted = int112(value); require(downcasted == value, "SafeCast: value doesn't fit in 112 bits"); } /** * @dev Returns the downcasted int104 from int256, reverting on * overflow (when the input is less than smallest int104 or * greater than largest int104). * * Counterpart to Solidity's `int104` operator. * * Requirements: * * - input must fit into 104 bits * * _Available since v4.7._ */ function toInt104(int256 value) internal pure returns (int104 downcasted) { downcasted = int104(value); require(downcasted == value, "SafeCast: value doesn't fit in 104 bits"); } /** * @dev Returns the downcasted int96 from int256, reverting on * overflow (when the input is less than smallest int96 or * greater than largest int96). * * Counterpart to Solidity's `int96` operator. * * Requirements: * * - input must fit into 96 bits * * _Available since v4.7._ */ function toInt96(int256 value) internal pure returns (int96 downcasted) { downcasted = int96(value); require(downcasted == value, "SafeCast: value doesn't fit in 96 bits"); } /** * @dev Returns the downcasted int88 from int256, reverting on * overflow (when the input is less than smallest int88 or * greater than largest int88). * * Counterpart to Solidity's `int88` operator. * * Requirements: * * - input must fit into 88 bits * * _Available since v4.7._ */ function toInt88(int256 value) internal pure returns (int88 downcasted) { downcasted = int88(value); require(downcasted == value, "SafeCast: value doesn't fit in 88 bits"); } /** * @dev Returns the downcasted int80 from int256, reverting on * overflow (when the input is less than smallest int80 or * greater than largest int80). * * Counterpart to Solidity's `int80` operator. * * Requirements: * * - input must fit into 80 bits * * _Available since v4.7._ */ function toInt80(int256 value) internal pure returns (int80 downcasted) { downcasted = int80(value); require(downcasted == value, "SafeCast: value doesn't fit in 80 bits"); } /** * @dev Returns the downcasted int72 from int256, reverting on * overflow (when the input is less than smallest int72 or * greater than largest int72). * * Counterpart to Solidity's `int72` operator. * * Requirements: * * - input must fit into 72 bits * * _Available since v4.7._ */ function toInt72(int256 value) internal pure returns (int72 downcasted) { downcasted = int72(value); require(downcasted == value, "SafeCast: value doesn't fit in 72 bits"); } /** * @dev Returns the downcasted int64 from int256, reverting on * overflow (when the input is less than smallest int64 or * greater than largest int64). * * Counterpart to Solidity's `int64` operator. * * Requirements: * * - input must fit into 64 bits * * _Available since v3.1._ */ function toInt64(int256 value) internal pure returns (int64 downcasted) { downcasted = int64(value); require(downcasted == value, "SafeCast: value doesn't fit in 64 bits"); } /** * @dev Returns the downcasted int56 from int256, reverting on * overflow (when the input is less than smallest int56 or * greater than largest int56). * * Counterpart to Solidity's `int56` operator. * * Requirements: * * - input must fit into 56 bits * * _Available since v4.7._ */ function toInt56(int256 value) internal pure returns (int56 downcasted) { downcasted = int56(value); require(downcasted == value, "SafeCast: value doesn't fit in 56 bits"); } /** * @dev Returns the downcasted int48 from int256, reverting on * overflow (when the input is less than smallest int48 or * greater than largest int48). * * Counterpart to Solidity's `int48` operator. * * Requirements: * * - input must fit into 48 bits * * _Available since v4.7._ */ function toInt48(int256 value) internal pure returns (int48 downcasted) { downcasted = int48(value); require(downcasted == value, "SafeCast: value doesn't fit in 48 bits"); } /** * @dev Returns the downcasted int40 from int256, reverting on * overflow (when the input is less than smallest int40 or * greater than largest int40). * * Counterpart to Solidity's `int40` operator. * * Requirements: * * - input must fit into 40 bits * * _Available since v4.7._ */ function toInt40(int256 value) internal pure returns (int40 downcasted) { downcasted = int40(value); require(downcasted == value, "SafeCast: value doesn't fit in 40 bits"); } /** * @dev Returns the downcasted int32 from int256, reverting on * overflow (when the input is less than smallest int32 or * greater than largest int32). * * Counterpart to Solidity's `int32` operator. * * Requirements: * * - input must fit into 32 bits * * _Available since v3.1._ */ function toInt32(int256 value) internal pure returns (int32 downcasted) { downcasted = int32(value); require(downcasted == value, "SafeCast: value doesn't fit in 32 bits"); } /** * @dev Returns the downcasted int24 from int256, reverting on * overflow (when the input is less than smallest int24 or * greater than largest int24). * * Counterpart to Solidity's `int24` operator. * * Requirements: * * - input must fit into 24 bits * * _Available since v4.7._ */ function toInt24(int256 value) internal pure returns (int24 downcasted) { downcasted = int24(value); require(downcasted == value, "SafeCast: value doesn't fit in 24 bits"); } /** * @dev Returns the downcasted int16 from int256, reverting on * overflow (when the input is less than smallest int16 or * greater than largest int16). * * Counterpart to Solidity's `int16` operator. * * Requirements: * * - input must fit into 16 bits * * _Available since v3.1._ */ function toInt16(int256 value) internal pure returns (int16 downcasted) { downcasted = int16(value); require(downcasted == value, "SafeCast: value doesn't fit in 16 bits"); } /** * @dev Returns the downcasted int8 from int256, reverting on * overflow (when the input is less than smallest int8 or * greater than largest int8). * * Counterpart to Solidity's `int8` operator. * * Requirements: * * - input must fit into 8 bits * * _Available since v3.1._ */ function toInt8(int256 value) internal pure returns (int8 downcasted) { downcasted = int8(value); require(downcasted == value, "SafeCast: value doesn't fit in 8 bits"); } /** * @dev Converts an unsigned uint256 into a signed int256. * * Requirements: * * - input must be less than or equal to maxInt256. * * _Available since v3.0._ */ function toInt256(uint256 value) internal pure returns (int256) { // Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive require(value <= uint256(type(int256).max), "SafeCast: value doesn't fit in an int256"); return int256(value); } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.8.0) (utils/math/SignedMath.sol) pragma solidity ^0.8.0; /** * @dev Standard signed math utilities missing in the Solidity language. */ library SignedMath { /** * @dev Returns the largest of two signed numbers. */ function max(int256 a, int256 b) internal pure returns (int256) { return a > b ? a : b; } /** * @dev Returns the smallest of two signed numbers. */ function min(int256 a, int256 b) internal pure returns (int256) { return a < b ? a : b; } /** * @dev Returns the average of two signed numbers without overflow. * The result is rounded towards zero. */ function average(int256 a, int256 b) internal pure returns (int256) { // Formula from the book "Hacker's Delight" int256 x = (a & b) + ((a ^ b) >> 1); return x + (int256(uint256(x) >> 255) & (a ^ b)); } /** * @dev Returns the absolute unsigned value of a signed value. */ function abs(int256 n) internal pure returns (uint256) { unchecked { // must be unchecked in order to support `n = type(int256).min` return uint256(n >= 0 ? n : -n); } } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (utils/ShortStrings.sol) pragma solidity ^0.8.8; import "./StorageSlot.sol"; // | string | 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA | // | length | 0x BB | type ShortString is bytes32; /** * @dev This library provides functions to convert short memory strings * into a `ShortString` type that can be used as an immutable variable. * * Strings of arbitrary length can be optimized using this library if * they are short enough (up to 31 bytes) by packing them with their * length (1 byte) in a single EVM word (32 bytes). Additionally, a * fallback mechanism can be used for every other case. * * Usage example: * * ```solidity * contract Named { * using ShortStrings for *; * * ShortString private immutable _name; * string private _nameFallback; * * constructor(string memory contractName) { * _name = contractName.toShortStringWithFallback(_nameFallback); * } * * function name() external view returns (string memory) { * return _name.toStringWithFallback(_nameFallback); * } * } * ``` */ library ShortStrings { // Used as an identifier for strings longer than 31 bytes. bytes32 private constant _FALLBACK_SENTINEL = 0x00000000000000000000000000000000000000000000000000000000000000FF; error StringTooLong(string str); error InvalidShortString(); /** * @dev Encode a string of at most 31 chars into a `ShortString`. * * This will trigger a `StringTooLong` error is the input string is too long. */ function toShortString(string memory str) internal pure returns (ShortString) { bytes memory bstr = bytes(str); if (bstr.length > 31) { revert StringTooLong(str); } return ShortString.wrap(bytes32(uint256(bytes32(bstr)) | bstr.length)); } /** * @dev Decode a `ShortString` back to a "normal" string. */ function toString(ShortString sstr) internal pure returns (string memory) { uint256 len = byteLength(sstr); // using `new string(len)` would work locally but is not memory safe. string memory str = new string(32); /// @solidity memory-safe-assembly assembly { mstore(str, len) mstore(add(str, 0x20), sstr) } return str; } /** * @dev Return the length of a `ShortString`. */ function byteLength(ShortString sstr) internal pure returns (uint256) { uint256 result = uint256(ShortString.unwrap(sstr)) & 0xFF; if (result > 31) { revert InvalidShortString(); } return result; } /** * @dev Encode a string into a `ShortString`, or write it to storage if it is too long. */ function toShortStringWithFallback(string memory value, string storage store) internal returns (ShortString) { if (bytes(value).length < 32) { return toShortString(value); } else { StorageSlot.getStringSlot(store).value = value; return ShortString.wrap(_FALLBACK_SENTINEL); } } /** * @dev Decode a string that was encoded to `ShortString` or written to storage using {setWithFallback}. */ function toStringWithFallback(ShortString value, string storage store) internal pure returns (string memory) { if (ShortString.unwrap(value) != _FALLBACK_SENTINEL) { return toString(value); } else { return store; } } /** * @dev Return the length of a string that was encoded to `ShortString` or written to storage using {setWithFallback}. * * WARNING: This will return the "byte length" of the string. This may not reflect the actual length in terms of * actual characters as the UTF-8 encoding of a single character can span over multiple bytes. */ function byteLengthWithFallback(ShortString value, string storage store) internal view returns (uint256) { if (ShortString.unwrap(value) != _FALLBACK_SENTINEL) { return byteLength(value); } else { return bytes(store).length; } } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (utils/StorageSlot.sol) // This file was procedurally generated from scripts/generate/templates/StorageSlot.js. pragma solidity ^0.8.0; /** * @dev Library for reading and writing primitive types to specific storage slots. * * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts. * This library helps with reading and writing to such slots without the need for inline assembly. * * The functions in this library return Slot structs that contain a `value` member that can be used to read or write. * * Example usage to set ERC1967 implementation slot: * ```solidity * contract ERC1967 { * bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; * * function _getImplementation() internal view returns (address) { * return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value; * } * * function _setImplementation(address newImplementation) internal { * require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract"); * StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation; * } * } * ``` * * _Available since v4.1 for `address`, `bool`, `bytes32`, `uint256`._ * _Available since v4.9 for `string`, `bytes`._ */ library StorageSlot { struct AddressSlot { address value; } struct BooleanSlot { bool value; } struct Bytes32Slot { bytes32 value; } struct Uint256Slot { uint256 value; } struct StringSlot { string value; } struct BytesSlot { bytes value; } /** * @dev Returns an `AddressSlot` with member `value` located at `slot`. */ function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) { /// @solidity memory-safe-assembly assembly { r.slot := slot } } /** * @dev Returns an `BooleanSlot` with member `value` located at `slot`. */ function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) { /// @solidity memory-safe-assembly assembly { r.slot := slot } } /** * @dev Returns an `Bytes32Slot` with member `value` located at `slot`. */ function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) { /// @solidity memory-safe-assembly assembly { r.slot := slot } } /** * @dev Returns an `Uint256Slot` with member `value` located at `slot`. */ function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) { /// @solidity memory-safe-assembly assembly { r.slot := slot } } /** * @dev Returns an `StringSlot` with member `value` located at `slot`. */ function getStringSlot(bytes32 slot) internal pure returns (StringSlot storage r) { /// @solidity memory-safe-assembly assembly { r.slot := slot } } /** * @dev Returns an `StringSlot` representation of the string storage pointer `store`. */ function getStringSlot(string storage store) internal pure returns (StringSlot storage r) { /// @solidity memory-safe-assembly assembly { r.slot := store.slot } } /** * @dev Returns an `BytesSlot` with member `value` located at `slot`. */ function getBytesSlot(bytes32 slot) internal pure returns (BytesSlot storage r) { /// @solidity memory-safe-assembly assembly { r.slot := slot } } /** * @dev Returns an `BytesSlot` representation of the bytes storage pointer `store`. */ function getBytesSlot(bytes storage store) internal pure returns (BytesSlot storage r) { /// @solidity memory-safe-assembly assembly { r.slot := store.slot } } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (utils/Strings.sol) pragma solidity ^0.8.0; import "./math/Math.sol"; import "./math/SignedMath.sol"; /** * @dev String operations. */ library Strings { bytes16 private constant _SYMBOLS = "0123456789abcdef"; uint8 private constant _ADDRESS_LENGTH = 20; /** * @dev Converts a `uint256` to its ASCII `string` decimal representation. */ function toString(uint256 value) internal pure returns (string memory) { unchecked { uint256 length = Math.log10(value) + 1; string memory buffer = new string(length); uint256 ptr; /// @solidity memory-safe-assembly assembly { ptr := add(buffer, add(32, length)) } while (true) { ptr--; /// @solidity memory-safe-assembly assembly { mstore8(ptr, byte(mod(value, 10), _SYMBOLS)) } value /= 10; if (value == 0) break; } return buffer; } } /** * @dev Converts a `int256` to its ASCII `string` decimal representation. */ function toString(int256 value) internal pure returns (string memory) { return string(abi.encodePacked(value < 0 ? "-" : "", toString(SignedMath.abs(value)))); } /** * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation. */ function toHexString(uint256 value) internal pure returns (string memory) { unchecked { return toHexString(value, Math.log256(value) + 1); } } /** * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length. */ function toHexString(uint256 value, uint256 length) internal pure returns (string memory) { bytes memory buffer = new bytes(2 * length + 2); buffer[0] = "0"; buffer[1] = "x"; for (uint256 i = 2 * length + 1; i > 1; --i) { buffer[i] = _SYMBOLS[value & 0xf]; value >>= 4; } require(value == 0, "Strings: hex length insufficient"); return string(buffer); } /** * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation. */ function toHexString(address addr) internal pure returns (string memory) { return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH); } /** * @dev Returns true if the two strings are equal. */ function equal(string memory a, string memory b) internal pure returns (bool) { return keccak256(bytes(a)) == keccak256(bytes(b)); } }
pragma solidity >=0.5.0; interface IUniswapV2Factory { event PairCreated(address indexed token0, address indexed token1, address pair, uint); function feeTo() external view returns (address); function feeToSetter() external view returns (address); function getPair(address tokenA, address tokenB) external view returns (address pair); function allPairs(uint) external view returns (address pair); function allPairsLength() external view returns (uint); function createPair(address tokenA, address tokenB) external returns (address pair); function setFeeTo(address) external; function setFeeToSetter(address) external; }
pragma solidity >=0.5.0; interface IUniswapV2Pair { event Approval(address indexed owner, address indexed spender, uint value); event Transfer(address indexed from, address indexed to, uint value); function name() external pure returns (string memory); function symbol() external pure returns (string memory); function decimals() external pure returns (uint8); function totalSupply() external view returns (uint); function balanceOf(address owner) external view returns (uint); function allowance(address owner, address spender) external view returns (uint); function approve(address spender, uint value) external returns (bool); function transfer(address to, uint value) external returns (bool); function transferFrom(address from, address to, uint value) external returns (bool); function DOMAIN_SEPARATOR() external view returns (bytes32); function PERMIT_TYPEHASH() external pure returns (bytes32); function nonces(address owner) external view returns (uint); function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external; event Mint(address indexed sender, uint amount0, uint amount1); event Burn(address indexed sender, uint amount0, uint amount1, address indexed to); event Swap( address indexed sender, uint amount0In, uint amount1In, uint amount0Out, uint amount1Out, address indexed to ); event Sync(uint112 reserve0, uint112 reserve1); function MINIMUM_LIQUIDITY() external pure returns (uint); function factory() external view returns (address); function token0() external view returns (address); function token1() external view returns (address); function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast); function price0CumulativeLast() external view returns (uint); function price1CumulativeLast() external view returns (uint); function kLast() external view returns (uint); function mint(address to) external returns (uint liquidity); function burn(address to) external returns (uint amount0, uint amount1); function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external; function skim(address to) external; function sync() external; function initialize(address, address) external; }
pragma solidity >=0.6.2; interface IUniswapV2Router01 { function factory() external pure returns (address); function WETH() external pure returns (address); function addLiquidity( address tokenA, address tokenB, uint amountADesired, uint amountBDesired, uint amountAMin, uint amountBMin, address to, uint deadline ) external returns (uint amountA, uint amountB, uint liquidity); function addLiquidityETH( address token, uint amountTokenDesired, uint amountTokenMin, uint amountETHMin, address to, uint deadline ) external payable returns (uint amountToken, uint amountETH, uint liquidity); function removeLiquidity( address tokenA, address tokenB, uint liquidity, uint amountAMin, uint amountBMin, address to, uint deadline ) external returns (uint amountA, uint amountB); function removeLiquidityETH( address token, uint liquidity, uint amountTokenMin, uint amountETHMin, address to, uint deadline ) external returns (uint amountToken, uint amountETH); function removeLiquidityWithPermit( address tokenA, address tokenB, uint liquidity, uint amountAMin, uint amountBMin, address to, uint deadline, bool approveMax, uint8 v, bytes32 r, bytes32 s ) external returns (uint amountA, uint amountB); function removeLiquidityETHWithPermit( address token, uint liquidity, uint amountTokenMin, uint amountETHMin, address to, uint deadline, bool approveMax, uint8 v, bytes32 r, bytes32 s ) external returns (uint amountToken, uint amountETH); function swapExactTokensForTokens( uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline ) external returns (uint[] memory amounts); function swapTokensForExactTokens( uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline ) external returns (uint[] memory amounts); function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline) external payable returns (uint[] memory amounts); function swapTokensForExactETH(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline) external returns (uint[] memory amounts); function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline) external returns (uint[] memory amounts); function swapETHForExactTokens(uint amountOut, address[] calldata path, address to, uint deadline) external payable returns (uint[] memory amounts); function quote(uint amountA, uint reserveA, uint reserveB) external pure returns (uint amountB); function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) external pure returns (uint amountOut); function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) external pure returns (uint amountIn); function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts); function getAmountsIn(uint amountOut, address[] calldata path) external view returns (uint[] memory amounts); }
pragma solidity >=0.6.2; import './IUniswapV2Router01.sol'; interface IUniswapV2Router02 is IUniswapV2Router01 { function removeLiquidityETHSupportingFeeOnTransferTokens( address token, uint liquidity, uint amountTokenMin, uint amountETHMin, address to, uint deadline ) external returns (uint amountETH); function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens( address token, uint liquidity, uint amountTokenMin, uint amountETHMin, address to, uint deadline, bool approveMax, uint8 v, bytes32 r, bytes32 s ) external returns (uint amountETH); function swapExactTokensForTokensSupportingFeeOnTransferTokens( uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline ) external; function swapExactETHForTokensSupportingFeeOnTransferTokens( uint amountOutMin, address[] calldata path, address to, uint deadline ) external payable; function swapExactTokensForETHSupportingFeeOnTransferTokens( uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline ) external; }
// 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: 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: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/utils/math/Math.sol"; import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; import "@uniswap/v2-core/contracts/interfaces/IUniswapV2Factory.sol"; import "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol"; import "@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol"; import "contracts/interfaces/IWETH.sol"; import "contracts/interfaces/IPriceFeedAggregator.sol"; import "contracts/interfaces/IReserveHolderV2.sol"; import "contracts/interfaces/IArbitrageERC20.sol"; import "contracts/library/ExternalContractAddresses.sol"; import "contracts/uniswap/libraries/UniswapV2Library.sol"; import "contracts/interfaces/IArbitrageV5.sol"; /// @title Contract for performing arbitrage /// @notice This contract is responsible for buying USC tokens and for keeping price of USC token pegged to target price /// @dev This contract represent second version of arbitrage contract, arbitrage is now private and all profit from arbitrage is kept in this contract contract ArbitrageV5 is IArbitrageV5, ReentrancyGuard, Ownable { using SafeERC20 for IERC20; using SafeERC20 for IArbitrageERC20; uint256 public constant BASE_PRICE = 1e8; uint256 public constant USC_TARGET_PRICE = 1e8; uint256 public constant POOL_FEE = 30; uint256 public constant MAX_FEE = 100_00; // F = (1 - pool_fee) on 18 decimals uint256 public constant F = ((MAX_FEE - POOL_FEE) * 1e18) / MAX_FEE; uint16 public constant MAX_PRICE_TOLERANCE = 100_00; address public constant WETH = ExternalContractAddresses.WETH; address public constant STETH = ExternalContractAddresses.stETH; IUniswapV2Router02 public constant swapRouter = IUniswapV2Router02(ExternalContractAddresses.UNI_V2_SWAP_ROUTER); IUniswapV2Factory public constant poolFactory = IUniswapV2Factory(ExternalContractAddresses.UNI_V2_POOL_FACTORY); IArbitrageERC20 public immutable USC; IArbitrageERC20 public immutable CHI; IPriceFeedAggregator public immutable priceFeedAggregator; IReserveHolderV2 public immutable reserveHolder; uint256 public pegPriceToleranceAbs; uint256 public mintBurnFee; uint256 public maxMintBurnPriceDiff; uint16 public maxMintBurnReserveTolerance; uint16 public chiPriceTolerance; uint16 public priceTolerance; bool public mintPaused; bool public burnPaused; uint256 public totalMintedUsc; // total amount of minted USC tokens during arbitrage when stablecoin is pegged mapping(address => bool) public isArbitrager; mapping(address => bool) public isPrivileged; mapping(address => uint256) public reserveMintTxLimit; mapping(address => uint256) public reserveBurnTxLimit; modifier onlyArbitrager() { if (!isArbitrager[msg.sender]) { revert NotArbitrager(msg.sender); } _; } modifier whenMintNotPaused() { if (mintPaused) { revert ContractIsPaused(); } _; } modifier whenBurnNotPaused() { if (burnPaused) { revert ContractIsPaused(); } _; } modifier onlyWhenMintableOrBurnable() { uint256 ethPrice = priceFeedAggregator.peek(WETH); uint256 uscSpotPrice = _calculateUscSpotPrice(ethPrice); if (!_almostEqualAbs(uscSpotPrice, USC_TARGET_PRICE, maxMintBurnPriceDiff)) { revert PriceIsNotPegged(); } (, uint256 reserveDiff, uint256 reserveValue) = _getReservesData(); if (reserveDiff > Math.mulDiv(reserveValue, maxMintBurnReserveTolerance, MAX_PRICE_TOLERANCE)) { revert ReserveDiffTooBig(); } _; } modifier onlyBellowMintLimit(address reserveAsset, uint256 amount) { if (reserveMintTxLimit[reserveAsset] < amount) { revert ReserveTxLimitExceeded(); } _; } modifier onlyBellowBurnLimit(address reserveAsset, uint256 amount) { if (reserveBurnTxLimit[reserveAsset] < amount) { revert ReserveTxLimitExceeded(); } _; } constructor( IArbitrageERC20 _USC, IArbitrageERC20 _CHI, IPriceFeedAggregator _priceFeedAggregator, IReserveHolderV2 _reserveHolder ) Ownable() { USC = _USC; CHI = _CHI; priceFeedAggregator = _priceFeedAggregator; reserveHolder = _reserveHolder; mintPaused = false; burnPaused = false; IERC20(STETH).approve(address(reserveHolder), type(uint256).max); } /// @inheritdoc IArbitrageV5 function setPegPriceToleranceAbs(uint256 _priceTolerance) external override onlyOwner { pegPriceToleranceAbs = _priceTolerance; } /// @inheritdoc IArbitrageV5 function setMintPause(bool isPaused) external onlyOwner { mintPaused = isPaused; } /// @inheritdoc IArbitrageV5 function setBurnPause(bool isPaused) external onlyOwner { burnPaused = isPaused; } /// @inheritdoc IArbitrageV5 function setPriceTolerance(uint16 _priceTolerance) external onlyOwner { if (_priceTolerance > MAX_PRICE_TOLERANCE) { revert ToleranceTooBig(_priceTolerance); } priceTolerance = _priceTolerance; emit SetPriceTolerance(_priceTolerance); } /// @inheritdoc IArbitrageV5 function setChiPriceTolerance(uint16 _chiPriceTolerance) external onlyOwner { if (_chiPriceTolerance > MAX_PRICE_TOLERANCE) { revert ToleranceTooBig(_chiPriceTolerance); } chiPriceTolerance = _chiPriceTolerance; emit SetChiPriceTolerance(_chiPriceTolerance); } /// @inheritdoc IArbitrageV5 function setMaxMintBurnPriceDiff(uint256 _maxMintBurnPriceDiff) external onlyOwner { maxMintBurnPriceDiff = _maxMintBurnPriceDiff; emit SetMaxMintBurnPriceDiff(_maxMintBurnPriceDiff); } /// @inheritdoc IArbitrageV5 function setMaxMintBurnReserveTolerance(uint16 _maxMintBurnReserveTolerance) external onlyOwner { if (_maxMintBurnReserveTolerance > MAX_PRICE_TOLERANCE) { revert ToleranceTooBig(_maxMintBurnReserveTolerance); } maxMintBurnReserveTolerance = _maxMintBurnReserveTolerance; emit SetMaxMintBurnReserveTolerance(_maxMintBurnReserveTolerance); } /// @inheritdoc IArbitrageV5 function setMintBurnFee(uint16 _mintBurnFee) external onlyOwner { if (_mintBurnFee > MAX_FEE) { revert FeeTooBig(_mintBurnFee); } mintBurnFee = _mintBurnFee; emit SetMintBurnFee(_mintBurnFee); } /// @inheritdoc IArbitrageV5 function setReserveMintTxLimit(address reserveAsset, uint256 limit) external onlyOwner { reserveMintTxLimit[reserveAsset] = limit; emit SetReserveMintTxLimit(reserveAsset, limit); } /// @inheritdoc IArbitrageV5 function setReserveBurnTxLimit(address reserveAsset, uint256 limit) external onlyOwner { reserveBurnTxLimit[reserveAsset] = limit; emit SetReserveBurnTxLimit(reserveAsset, limit); } /// @inheritdoc IArbitrageV5 function updateArbitrager(address arbitrager, bool status) external onlyOwner { isArbitrager[arbitrager] = status; emit UpdateArbitrager(arbitrager, status); } /// @inheritdoc IArbitrageV5 function updatePrivileged(address account, bool status) external onlyOwner { isPrivileged[account] = status; emit UpdatePrivileged(account, status); } /// @inheritdoc IArbitrageV5 function claimRewards(IERC20[] memory tokens, uint256[] memory amounts) external onlyOwner { for (uint256 i = 0; i < tokens.length; i++) { IERC20 token = tokens[i]; uint256 amount = amounts[i]; token.safeTransfer(msg.sender, amount); if (address(token) == address(USC)) { if (amount < totalMintedUsc) { totalMintedUsc -= amount; } else { totalMintedUsc = 0; } } } } function rewardUSC(uint256 amount) external onlyOwner { USC.safeTransferFrom(msg.sender, address(this), amount); totalMintedUsc += amount; emit RewardUSC(amount); } /// @inheritdoc IArbitrageV5 function mint( address token, uint256 amount, address receiver ) external whenMintNotPaused nonReentrant onlyWhenMintableOrBurnable onlyBellowMintLimit(token, amount) returns (uint256) { uint256 fee = Math.mulDiv(amount, mintBurnFee, MAX_FEE); uint256 amountAfterFee = amount - fee; IERC20(token).safeTransferFrom(msg.sender, address(this), amount); IERC20(token).approve(address(reserveHolder), amountAfterFee); reserveHolder.deposit(token, amountAfterFee); return _mint(amountAfterFee, token, receiver); } /// @inheritdoc IArbitrageV5 function mint( address receiver ) external payable whenMintNotPaused nonReentrant onlyWhenMintableOrBurnable onlyBellowMintLimit(address(WETH), msg.value) returns (uint256) { uint256 ethAmount = msg.value; uint256 fee = Math.mulDiv(ethAmount, mintBurnFee, MAX_FEE); uint256 ethAmountAfterFee = ethAmount - fee; IWETH(WETH).deposit{value: ethAmount}(); IERC20(WETH).safeTransfer(address(reserveHolder), ethAmountAfterFee); return _mint(ethAmountAfterFee, WETH, receiver); } /// @inheritdoc IArbitrageV5 function burn( uint256 amount, address reserveToReceive ) external whenBurnNotPaused nonReentrant onlyWhenMintableOrBurnable onlyBellowBurnLimit(reserveToReceive, amount) returns (uint256) { uint256 reservePrice = priceFeedAggregator.peek(reserveToReceive); USC.safeTransferFrom(msg.sender, address(this), amount); amount -= (amount * mintBurnFee) / MAX_FEE; USC.burn(amount); uint256 reserveAmountToRedeem = Math.mulDiv(amount, USC_TARGET_PRICE, reservePrice); reserveHolder.redeem(reserveAmountToRedeem, reserveToReceive); IERC20(reserveToReceive).safeTransfer(msg.sender, reserveAmountToRedeem); emit Burn(msg.sender, amount, reserveAmountToRedeem, reserveToReceive); return reserveAmountToRedeem; } /// @inheritdoc IArbitrageV5 function executeArbitrageWithReserveSell( uint256 maxChiSpotPrice, SwapParams[] calldata swapParams ) external returns (uint256) { for (uint256 i = 0; i < swapParams.length; i++) { reserveHolder.swapReserveForEth(swapParams[i].reserveAsset, swapParams[i].amountIn, swapParams[i].minAmountOut); } return executeArbitrage(maxChiSpotPrice); } /// @inheritdoc IArbitrageV5 function executeArbitrage(uint256 maxChiSpotPrice) public override nonReentrant onlyArbitrager returns (uint256) { _validateArbitrage(maxChiSpotPrice); return _executeArbitrage(); } /// @inheritdoc IArbitrageV5 function getArbitrageData() public view returns (bool isPriceAtPeg, bool isPriceAboveTarget, bool isExcessOfReserves, uint256 reserveDiff) { uint256 ethPrice = priceFeedAggregator.peek(WETH); uint256 uscPrice = _getAndValidateUscPrice(ethPrice); uint256 reserveValue; (isExcessOfReserves, reserveDiff, reserveValue) = _getReservesData(); isPriceAboveTarget = uscPrice >= USC_TARGET_PRICE; return ( _almostEqualAbs(uscPrice, USC_TARGET_PRICE, pegPriceToleranceAbs), isPriceAboveTarget, isExcessOfReserves, reserveDiff ); } function _executeArbitrage() internal returns (uint256) { (bool isPriceAtPeg, bool isPriceAboveTarget, bool isExcessOfReserves, uint256 reserveDiff) = getArbitrageData(); uint256 ethPrice = priceFeedAggregator.peek(WETH); if (isPriceAtPeg) { if (isExcessOfReserves) { return _arbitrageAtPegExcessOfReserves(reserveDiff, ethPrice); } else { return _arbitrageAtPegDeficitOfReserves(reserveDiff, ethPrice); } } else if (isPriceAboveTarget) { if (isExcessOfReserves) { return _arbitrageAbovePegExcessOfReserves(reserveDiff, ethPrice); } else { return _arbitrageAbovePegDeficitOfReserves(reserveDiff, ethPrice); } } else { if (isExcessOfReserves) { return _arbitrageBellowPegExcessOfReserves(reserveDiff, ethPrice); } else { return _arbitrageBellowPegDeficitOfReserves(reserveDiff, ethPrice); } } } function _mint(uint256 amount, address token, address receiver) private returns (uint256) { uint256 usdValue; if (token == WETH) { uint256 ethPrice = priceFeedAggregator.peek(WETH); usdValue = _convertTokenAmountToUsdValue(amount, ethPrice); } else { uint256 tokenPrice = priceFeedAggregator.peek(token); usdValue = _convertTokenAmountToUsdValue(amount, tokenPrice); } uint256 uscAmountToMint = _convertUsdValueToTokenAmount(usdValue, USC_TARGET_PRICE); USC.mint(receiver, uscAmountToMint); emit Mint(msg.sender, token, amount, uscAmountToMint); return uscAmountToMint; } function _arbitrageAbovePegExcessOfReserves(uint256 reserveDiff, uint256 ethPrice) private returns (uint256) { uint256 deltaUSC = _calculateDeltaUSC(ethPrice); USC.mint(address(this), deltaUSC); uint256 ethAmountReceived = _swap(address(USC), WETH, deltaUSC); uint256 deltaUsd = _convertTokenAmountToUsdValue(deltaUSC, USC_TARGET_PRICE); uint256 deltaInETH = _convertUsdValueToTokenAmount(deltaUsd, ethPrice); if (deltaInETH > ethAmountReceived) { revert DeltaBiggerThanAmountReceivedETH(deltaInETH, ethAmountReceived); } uint256 ethAmountToSwap; uint256 ethAmountForReserves; if (deltaUsd > reserveDiff) { ethAmountToSwap = _convertUsdValueToTokenAmount(reserveDiff, ethPrice); ethAmountForReserves = _convertUsdValueToTokenAmount(deltaUsd, ethPrice) - ethAmountToSwap; IERC20(WETH).safeTransfer(address(reserveHolder), ethAmountForReserves); } else { ethAmountToSwap = _convertUsdValueToTokenAmount(deltaUsd, ethPrice); } uint256 chiAmountReceived = _swap(WETH, address(CHI), ethAmountToSwap); CHI.burn(chiAmountReceived); uint256 rewardAmount = ethAmountReceived - ethAmountToSwap - ethAmountForReserves; uint256 rewardValue = _convertTokenAmountToUsdValue(rewardAmount, ethPrice); emit ExecuteArbitrage(msg.sender, 1, deltaUsd, reserveDiff, ethPrice, rewardValue); return rewardValue; } function _arbitrageAbovePegDeficitOfReserves(uint256 reserveDiff, uint256 ethPrice) private returns (uint256) { uint256 deltaUSC = _calculateDeltaUSC(ethPrice); USC.mint(address(this), deltaUSC); uint256 ethAmountReceived = _swap(address(USC), WETH, deltaUSC); uint256 deltaUsd = _convertTokenAmountToUsdValue(deltaUSC, USC_TARGET_PRICE); uint256 deltaInETH = _convertUsdValueToTokenAmount(deltaUsd, ethPrice); if (deltaInETH > ethAmountReceived) { revert DeltaBiggerThanAmountReceivedETH(deltaInETH, ethAmountReceived); } IERC20(WETH).safeTransfer(address(reserveHolder), deltaInETH); uint256 rewardAmount = ethAmountReceived - deltaInETH; uint256 rewardValue = _convertTokenAmountToUsdValue(rewardAmount, ethPrice); emit ExecuteArbitrage(msg.sender, 2, deltaUsd, reserveDiff, ethPrice, rewardValue); return rewardValue; } function _arbitrageBellowPegExcessOfReserves(uint256 reserveDiff, uint256 ethPrice) private returns (uint256) { uint256 uscAmountToFreeze; uint256 uscAmountToBurn; uint256 deltaETH = _calculateDeltaETH(ethPrice); uint256 deltaUsd = _convertTokenAmountToUsdValue(deltaETH, ethPrice); if (deltaUsd > reserveDiff) { uscAmountToFreeze = _convertUsdValueToTokenAmount(reserveDiff, USC_TARGET_PRICE); uscAmountToBurn = _convertUsdValueToTokenAmount(deltaUsd - reserveDiff, USC_TARGET_PRICE); } else { uscAmountToFreeze = _convertUsdValueToTokenAmount(deltaUsd, USC_TARGET_PRICE); } reserveHolder.redeem(deltaETH, address(WETH)); uint256 uscAmountReceived = _swap(WETH, address(USC), deltaETH); if (uscAmountReceived < uscAmountToFreeze) { uscAmountToFreeze = uscAmountReceived; } if (uscAmountToBurn > 0) { USC.burn(uscAmountToBurn); } uint256 rewardAmount = uscAmountReceived - uscAmountToFreeze - uscAmountToBurn; uint256 rewardValue = _convertTokenAmountToUsdValue(rewardAmount, USC_TARGET_PRICE); emit ExecuteArbitrage(msg.sender, 3, deltaUsd, reserveDiff, ethPrice, rewardValue); return rewardValue; } function _arbitrageBellowPegDeficitOfReserves(uint256 reserveDiff, uint256 ethPrice) private returns (uint256) { uint256 ethAmountToRedeem; uint256 ethAmountFromChi; uint256 deltaETH = _calculateDeltaETH(ethPrice); uint256 deltaUsd = _convertTokenAmountToUsdValue(deltaETH, ethPrice); if (deltaUsd > reserveDiff) { ethAmountFromChi = _convertUsdValueToTokenAmount(reserveDiff, ethPrice); ethAmountToRedeem = deltaETH - ethAmountFromChi; reserveHolder.redeem(ethAmountToRedeem, address(WETH)); } else { ethAmountFromChi = deltaETH; } uint256 uscAmountToBurn = _convertUsdValueToTokenAmount(deltaUsd, USC_TARGET_PRICE); uint256 uscAmountReceived; { if (ethAmountFromChi > 0) { uint256 chiAmountToMint = _getAmountInForAmountOut(address(CHI), WETH, ethAmountFromChi); CHI.mint(address(this), chiAmountToMint); _swap(address(CHI), WETH, chiAmountToMint); } uscAmountReceived = _swap(WETH, address(USC), ethAmountFromChi + ethAmountToRedeem); } if (uscAmountToBurn > 0) { USC.burn(uscAmountToBurn); } { uint256 rewardAmount = uscAmountReceived - uscAmountToBurn; uint256 rewardValue = _convertTokenAmountToUsdValue(rewardAmount, USC_TARGET_PRICE); emit ExecuteArbitrage(msg.sender, 4, deltaUsd, reserveDiff, ethPrice, rewardValue); return rewardValue; } } function _arbitrageAtPegExcessOfReserves(uint256 reserveDiff, uint256 ethPrice) private returns (uint256) { uint256 uscAmountToMint = _convertUsdValueToTokenAmount(reserveDiff, USC_TARGET_PRICE); USC.mint(address(this), uscAmountToMint); totalMintedUsc += uscAmountToMint; emit ExecuteArbitrage(msg.sender, 5, 0, reserveDiff, ethPrice, 0); return 0; } function _arbitrageAtPegDeficitOfReserves(uint256 reserveDiff, uint256 ethPrice) private returns (uint256) { uint256 reserveDiffInUsc = _convertUsdValueToTokenAmount(reserveDiff, USC_TARGET_PRICE); uint256 uscAmountToBurn; uint256 ethToGet; if (reserveDiffInUsc > totalMintedUsc) { uscAmountToBurn = totalMintedUsc; uint256 ethToGetInUsd = _convertTokenAmountToUsdValue(reserveDiffInUsc - totalMintedUsc, USC_TARGET_PRICE); ethToGet = _convertUsdValueToTokenAmount(ethToGetInUsd, ethPrice); } else { uscAmountToBurn = reserveDiffInUsc; ethToGet = 0; } uint256 chiToCoverEth; if (ethToGet > 0) { chiToCoverEth = _getAmountInForAmountOut(address(CHI), WETH, ethToGet); } CHI.mint(address(this), chiToCoverEth); if (ethToGet > 0) { uint256 ethReceived = _swap(address(CHI), WETH, chiToCoverEth); IERC20(WETH).safeTransfer(address(reserveHolder), ethReceived); } if (uscAmountToBurn > 0) { USC.burn(uscAmountToBurn); totalMintedUsc -= uscAmountToBurn; } emit ExecuteArbitrage(msg.sender, 6, 0, reserveDiff, ethPrice, 0); return 0; } function _getReservesData() public view returns (bool isExcessOfReserves, uint256 reserveDiff, uint256 reserveValue) { reserveValue = reserveHolder.getReserveValue(); uint256 uscTotalSupplyValue = _convertTokenAmountToUsdValue(USC.totalSupply(), USC_TARGET_PRICE); if (reserveValue > uscTotalSupplyValue) { isExcessOfReserves = true; reserveDiff = (reserveValue - uscTotalSupplyValue); } else { isExcessOfReserves = false; reserveDiff = (uscTotalSupplyValue - reserveValue); } } function _getAndValidateUscPrice(uint256 ethPrice) private view returns (uint256) { uint256 uscPrice = priceFeedAggregator.peek(address(USC)); uint256 uscSpotPrice = _calculateUscSpotPrice(ethPrice); uint256 priceDiff = _absDiff(uscSpotPrice, uscPrice); uint256 maxPriceDiff = Math.mulDiv(uscPrice, priceTolerance, MAX_PRICE_TOLERANCE); if (priceDiff > maxPriceDiff) { revert PriceSlippageTooBig(); } return uscSpotPrice; } function _validateArbitrage(uint256 maxChiSpotPrice) private view { if (!isPrivileged[msg.sender]) { uint256 ethPrice = priceFeedAggregator.peek(WETH); uint256 chiSpotPrice = _calculateChiSpotPrice(ethPrice); if (maxChiSpotPrice != 0 && chiSpotPrice > maxChiSpotPrice) { revert ChiSpotPriceTooBig(); } // If max chi spot price is not specified we need to check for twap difference if (maxChiSpotPrice == 0) { uint256 chiOraclePrice = priceFeedAggregator.peek(address(CHI)); if (!_almostEqualRel(chiSpotPrice, chiOraclePrice, chiPriceTolerance)) { revert ChiPriceNotPegged(chiSpotPrice, chiOraclePrice); } } } } // input ethPrice has 8 decimals // returns result with 8 decimals function _calculateUscSpotPrice(uint256 ethPrice) private view returns (uint256) { (uint256 reserveUSC, uint256 reserveWETH) = UniswapV2Library.getReserves( address(poolFactory), address(USC), address(WETH) ); uint256 uscFor1ETH = UniswapV2Library.quote(1 ether, reserveWETH, reserveUSC); return Math.mulDiv(ethPrice, 1 ether, uscFor1ETH); } // input ethPrice has 8 decimals // returns result with 8 decimals function _calculateChiSpotPrice(uint256 ethPrice) private view returns (uint256) { (uint256 reserveCHI, uint256 reserveWETH) = UniswapV2Library.getReserves( address(poolFactory), address(CHI), address(WETH) ); uint256 chiFor1ETH = UniswapV2Library.quote(1 ether, reserveWETH, reserveCHI); return Math.mulDiv(ethPrice, 1 ether, chiFor1ETH); } // what amount of In tokens to put in pool to make price: 1 tokenOut = (priceOut / priceIn) tokenIn // assuming reserves are on 18 decimals, prices are on 8 decimals function _calculateDelta( uint256 reserveIn, uint256 priceIn, uint256 reserveOut, uint256 priceOut ) public pure returns (uint256) { // F = (1 - pool_fee) = 0.997 on 18 decimals, in square root formula a = F // parameter `b` in square root formula, b = rIn * (1+f) , on 18 decimals uint256 b = Math.mulDiv(reserveIn, 1e18 + F, 1e18); uint256 b_sqr = Math.mulDiv(b, b, 1e18); // parameter `c` in square root formula, c = rIn^2 - (rIn * rOut * priceOut) / priceIn uint256 c_1 = Math.mulDiv(reserveIn, reserveIn, 1e18); uint256 c_2 = Math.mulDiv(Math.mulDiv(reserveIn, reserveOut, 1e18), priceOut, priceIn); uint256 c; uint256 root; if (c_1 > c_2) { c = c_1 - c_2; // d = 4ac uint256 d = Math.mulDiv(4 * F, c, 1e18); // root = sqrt(b^2 - 4ac) // multiplying by 10^9 to get back to 18 decimals root = Math.sqrt(b_sqr - d) * 1e9; } else { c = c_2 - c_1; // d = 4ac uint256 d = Math.mulDiv(4 * F, c, 1e18); // root = sqrt(b^2 - 4ac) -> in this case `c` is negative, so we add `d` to `b^2` // multiplying by 10^9 to get back to 18 decimals root = Math.sqrt(b_sqr + d) * 1e9; } // delta = (-b + root) / 2*f uint256 delta = Math.mulDiv(1e18, root - b, 2 * F); return delta; } // given ethPrice is on 8 decimals // how many USC to put in pool to make price: 1 ETH = ethPrice * USC function _calculateDeltaUSC(uint256 ethPrice) public view returns (uint256) { (uint256 reserveUSC, uint256 reserveWETH) = UniswapV2Library.getReserves( address(poolFactory), address(USC), address(WETH) ); return _calculateDelta(reserveUSC, USC_TARGET_PRICE, reserveWETH, ethPrice); } // how many ETH to put in pool to make price: 1 ETH = ethPrice * USC function _calculateDeltaETH(uint256 ethPrice) public view returns (uint256) { (uint256 reserveUSC, uint256 reserveWETH) = UniswapV2Library.getReserves( address(poolFactory), address(USC), address(WETH) ); return _calculateDelta(reserveWETH, ethPrice, reserveUSC, USC_TARGET_PRICE); } function _makePath(address t1, address t2) internal pure returns (address[] memory path) { path = new address[](2); path[0] = t1; path[1] = t2; } function _makePath(address t1, address t2, address t3) internal pure returns (address[] memory path) { path = new address[](3); path[0] = t1; path[1] = t2; path[2] = t3; } function _swap(address tokenIn, address tokenOut, uint256 amount) private returns (uint256) { address[] memory path; if (tokenIn != WETH && tokenOut != WETH) { path = _makePath(tokenIn, WETH, tokenOut); } else { path = _makePath(tokenIn, tokenOut); } IERC20(tokenIn).approve(address(swapRouter), amount); uint256[] memory amounts = swapRouter.swapExactTokensForTokens(amount, 0, path, address(this), block.timestamp); uint256 amountReceived = amounts[path.length - 1]; return amountReceived; } function _getAmountInForAmountOut(address tIn, address tOut, uint256 amountOut) internal view returns (uint256) { (uint256 rIn, uint256 rOut) = UniswapV2Library.getReserves(address(poolFactory), tIn, tOut); return UniswapV2Library.getAmountIn(amountOut, rIn, rOut); } function _convertUsdValueToTokenAmount(uint256 usdValue, uint256 price) internal pure returns (uint256) { return Math.mulDiv(usdValue, 1e18, price); } function _convertTokenAmountToUsdValue(uint256 amount, uint256 price) internal pure returns (uint256) { return Math.mulDiv(amount, price, 1e18); } function _absDiff(uint256 a, uint256 b) internal pure returns (uint256) { return (a > b) ? a - b : b - a; } function _almostEqualAbs(uint256 price1, uint256 price2, uint256 delta) internal pure returns (bool) { return _absDiff(price1, price2) <= delta; } function _almostEqualRel(uint256 price1, uint256 price2, uint256 delta) internal pure returns (bool) { (uint256 highPrice, uint256 lowPrice) = price1 > price2 ? (price1, price2) : (price2, price1); uint256 priceDiff = highPrice - lowPrice; uint256 maxPriceDiff = Math.mulDiv(highPrice, delta, MAX_PRICE_TOLERANCE); return priceDiff <= maxPriceDiff; } receive() external payable {} }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/access/Ownable.sol"; import "../interfaces/IMintable.sol"; /// @title Mintable /// @notice CHI and USC tokens should inherit from this contract contract Mintable is IMintable, Ownable { mapping(address account => bool status) public isMinter; modifier onlyMinter() { if (!isMinter[msg.sender]) { revert NotMinter(msg.sender); } _; } /// @inheritdoc IMintable function updateMinter(address account, bool status) external onlyOwner { if (account == address(0)) { revert ZeroAddress(); } isMinter[account] = status; emit UpdateMinter(account, status); } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/utils/math/Math.sol"; import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; import "@uniswap/v2-core/contracts/interfaces/IUniswapV2Factory.sol"; import "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol"; import "@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol"; import "contracts/interfaces/IWETH.sol"; import "contracts/interfaces/IPriceFeedAggregator.sol"; import "contracts/interfaces/IReserveHolder.sol"; import "contracts/interfaces/IArbitrageERC20.sol"; import "contracts/interfaces/IRewardController.sol"; import "contracts/library/ExternalContractAddresses.sol"; import "contracts/uniswap/libraries/UniswapV2Library.sol"; import "contracts/interfaces/IArbitrage.sol"; /// @title Contract for performing arbitrage /// @notice This contract is responsible for buying USC tokens and for keeping price of USC token pegged to target price /// @dev This contract does not hold any assets contract Arbitrage is IArbitrage, ReentrancyGuard, Ownable { using SafeERC20 for IERC20; using SafeERC20 for IArbitrageERC20; uint256 public constant BASE_PRICE = 1e8; uint256 public constant USC_TARGET_PRICE = 1e8; uint256 public constant EQ_PRICE_DELTA = 1000 wei; uint256 public constant POOL_FEE = 30; uint256 public constant MAX_FEE = 100_00; // F = (1 - pool_fee) on 18 decimals uint256 public constant F = ((MAX_FEE - POOL_FEE) * 1e18) / MAX_FEE; uint16 public constant MAX_PRICE_TOLERANCE = 100_00; address public constant WETH = ExternalContractAddresses.WETH; address public constant STETH = ExternalContractAddresses.stETH; IUniswapV2Router02 public constant swapRouter = IUniswapV2Router02(ExternalContractAddresses.UNI_V2_SWAP_ROUTER); IUniswapV2Factory public constant poolFactory = IUniswapV2Factory(ExternalContractAddresses.UNI_V2_POOL_FACTORY); IArbitrageERC20 public immutable USC; IArbitrageERC20 public immutable CHI; IRewardController public immutable rewardController; IPriceFeedAggregator public immutable priceFeedAggregator; IReserveHolder public immutable reserveHolder; uint256 public maxMintPriceDiff; uint16 public priceTolerance; constructor( IArbitrageERC20 _USC, IArbitrageERC20 _CHI, IRewardController _rewardController, IPriceFeedAggregator _priceFeedAggregator, IReserveHolder _reserveHolder ) Ownable() { USC = _USC; CHI = _CHI; rewardController = _rewardController; priceFeedAggregator = _priceFeedAggregator; reserveHolder = _reserveHolder; IERC20(USC).approve(address(rewardController), type(uint256).max); IERC20(STETH).approve(address(reserveHolder), type(uint256).max); } /// @inheritdoc IArbitrage function setPriceTolerance(uint16 _priceTolerance) external onlyOwner { if (_priceTolerance > MAX_PRICE_TOLERANCE) { revert ToleranceTooBig(_priceTolerance); } priceTolerance = _priceTolerance; emit SetPriceTolerance(_priceTolerance); } /// @inheritdoc IArbitrage function setMaxMintPriceDiff(uint256 _maxMintPriceDiff) external onlyOwner { maxMintPriceDiff = _maxMintPriceDiff; emit SetMaxMintPriceDiff(_maxMintPriceDiff); } /// @inheritdoc IArbitrage function mint() external payable nonReentrant returns (uint256) { uint256 ethAmount = msg.value; IWETH(WETH).deposit{value: ethAmount}(); IERC20(WETH).safeTransfer(address(reserveHolder), ethAmount); return _mint(ethAmount, WETH); } /// @inheritdoc IArbitrage function mintWithWETH(uint256 wethAmount) external nonReentrant returns (uint256) { IERC20(WETH).safeTransferFrom(msg.sender, address(reserveHolder), wethAmount); return _mint(wethAmount, WETH); } /// @inheritdoc IArbitrage function mintWithStETH(uint256 stETHAmount) external nonReentrant returns (uint256) { IERC20(STETH).safeTransferFrom(msg.sender, address(this), stETHAmount); reserveHolder.deposit(IERC20(STETH).balanceOf(address(this))); return _mint(stETHAmount, STETH); } /// @inheritdoc IArbitrage function executeArbitrage() public override nonReentrant returns (uint256) { return _executeArbitrage(); } /// @inheritdoc IArbitrage function getArbitrageData() public view returns (bool isPriceAboveTarget, bool isExcessOfReserves, uint256 reserveDiff, uint256 discount) { uint256 ethPrice = priceFeedAggregator.peek(WETH); uint256 uscPrice = _getAndValidateUscPrice(ethPrice); uint256 reserveValue; (isExcessOfReserves, reserveDiff, reserveValue) = _getReservesData(); isPriceAboveTarget = uscPrice >= USC_TARGET_PRICE; //If prices are equal delta does not need to be calculated if (_almostEqualAbs(uscPrice, USC_TARGET_PRICE, EQ_PRICE_DELTA)) { discount = Math.mulDiv(reserveDiff, BASE_PRICE, reserveValue); } } function _executeArbitrage() internal returns (uint256) { (bool isPriceAboveTarget, bool isExcessOfReserves, uint256 reserveDiff, uint256 discount) = getArbitrageData(); uint256 ethPrice = priceFeedAggregator.peek(WETH); if (discount != 0) { if (isExcessOfReserves) { return _arbitrageAtPegExcessOfReserves(reserveDiff, discount, ethPrice); } else { return _arbitrageAtPegDeficitOfReserves(reserveDiff, discount, ethPrice); } } else if (isPriceAboveTarget) { if (isExcessOfReserves) { return _arbitrageAbovePegExcessOfReserves(reserveDiff, ethPrice); } else { return _arbitrageAbovePegDeficitOfReserves(reserveDiff, ethPrice); } } else { if (isExcessOfReserves) { return _arbitrageBellowPegExcessOfReserves(reserveDiff, ethPrice); } else { return _arbitrageBellowPegDeficitOfReserves(reserveDiff, ethPrice); } } } function _mint(uint256 amount, address token) private returns (uint256) { uint256 ethPrice = priceFeedAggregator.peek(WETH); uint256 uscSpotPrice = _calculateUscSpotPrice(ethPrice); if (!_almostEqualAbs(uscSpotPrice, USC_TARGET_PRICE, maxMintPriceDiff)) { _executeArbitrage(); } uint256 usdValue; if (token == WETH) { usdValue = _convertTokenAmountToUsdValue(amount, ethPrice); } else { uint256 tokenPrice = priceFeedAggregator.peek(token); usdValue = _convertTokenAmountToUsdValue(amount, tokenPrice); } uint256 uscAmountToMint = _convertUsdValueToTokenAmount(usdValue, USC_TARGET_PRICE); USC.mint(msg.sender, uscAmountToMint); emit Mint(msg.sender, token, amount, uscAmountToMint); return uscAmountToMint; } function _arbitrageAbovePegExcessOfReserves(uint256 reserveDiff, uint256 ethPrice) private returns (uint256) { uint256 deltaUSC = _calculateDeltaUSC(ethPrice); USC.mint(address(this), deltaUSC); uint256 ethAmountReceived = _swap(address(USC), WETH, deltaUSC); uint256 deltaUsd = _convertTokenAmountToUsdValue(deltaUSC, USC_TARGET_PRICE); uint256 deltaInETH = _convertUsdValueToTokenAmount(deltaUsd, ethPrice); if (deltaInETH > ethAmountReceived) { revert DeltaBiggerThanAmountReceivedETH(deltaInETH, ethAmountReceived); } uint256 ethAmountToSwap; uint256 ethAmountForReserves; if (deltaUsd > reserveDiff) { ethAmountToSwap = _convertUsdValueToTokenAmount(reserveDiff, ethPrice); ethAmountForReserves = _convertUsdValueToTokenAmount(deltaUsd, ethPrice) - ethAmountToSwap; IERC20(WETH).safeTransfer(address(reserveHolder), ethAmountForReserves); } else { ethAmountToSwap = _convertUsdValueToTokenAmount(deltaUsd, ethPrice); } uint256 chiAmountReceived = _swap(WETH, address(CHI), ethAmountToSwap); CHI.burn(chiAmountReceived); uint256 rewardAmount = ethAmountReceived - ethAmountToSwap - ethAmountForReserves; IERC20(WETH).safeTransfer(msg.sender, rewardAmount); uint256 rewardValue = _convertTokenAmountToUsdValue(rewardAmount, ethPrice); emit ExecuteArbitrage(msg.sender, 1, deltaUsd, reserveDiff, ethPrice, rewardValue); return rewardValue; } function _arbitrageAbovePegDeficitOfReserves(uint256 reserveDiff, uint256 ethPrice) private returns (uint256) { uint256 deltaUSC = _calculateDeltaUSC(ethPrice); USC.mint(address(this), deltaUSC); uint256 ethAmountReceived = _swap(address(USC), WETH, deltaUSC); uint256 deltaUsd = _convertTokenAmountToUsdValue(deltaUSC, USC_TARGET_PRICE); uint256 deltaInETH = _convertUsdValueToTokenAmount(deltaUsd, ethPrice); if (deltaInETH > ethAmountReceived) { revert DeltaBiggerThanAmountReceivedETH(deltaInETH, ethAmountReceived); } IERC20(WETH).safeTransfer(address(reserveHolder), deltaInETH); uint256 rewardAmount = ethAmountReceived - deltaInETH; IERC20(WETH).safeTransfer(msg.sender, rewardAmount); uint256 rewardValue = _convertTokenAmountToUsdValue(rewardAmount, ethPrice); emit ExecuteArbitrage(msg.sender, 2, deltaUsd, reserveDiff, ethPrice, rewardValue); return rewardValue; } function _arbitrageBellowPegExcessOfReserves(uint256 reserveDiff, uint256 ethPrice) private returns (uint256) { uint256 uscAmountToFreeze; uint256 uscAmountToBurn; uint256 ethAmountToRedeem; uint256 ethAmountFromChi; uint256 deltaETH = _calculateDeltaETH(ethPrice); uint256 deltaUsd = _convertTokenAmountToUsdValue(deltaETH, ethPrice); if (deltaUsd > reserveDiff) { ethAmountToRedeem = _convertUsdValueToTokenAmount(reserveDiff, ethPrice); uscAmountToFreeze = _convertUsdValueToTokenAmount(reserveDiff, USC_TARGET_PRICE); uscAmountToBurn = _convertUsdValueToTokenAmount(deltaUsd - reserveDiff, USC_TARGET_PRICE); uint256 chiAmountToMint = _getAmountInForAmountOut(address(CHI), WETH, deltaETH - ethAmountToRedeem); CHI.mint(address(this), chiAmountToMint); ethAmountFromChi = _swap(address(CHI), WETH, chiAmountToMint); } else { ethAmountToRedeem = deltaETH; uscAmountToFreeze = _convertUsdValueToTokenAmount(deltaUsd, USC_TARGET_PRICE); } reserveHolder.redeem(ethAmountToRedeem); uint256 uscAmountReceived = _swap(WETH, address(USC), ethAmountToRedeem + ethAmountFromChi); if (uscAmountReceived < uscAmountToFreeze) { uscAmountToFreeze = uscAmountReceived; } rewardController.rewardUSC(uscAmountToFreeze); if (uscAmountToBurn > 0) { USC.burn(uscAmountToBurn); } uint256 rewardAmount = uscAmountReceived - uscAmountToFreeze - uscAmountToBurn; USC.safeTransfer(msg.sender, rewardAmount); uint256 rewardValue = _convertTokenAmountToUsdValue(rewardAmount, USC_TARGET_PRICE); emit ExecuteArbitrage(msg.sender, 3, deltaUsd, reserveDiff, ethPrice, rewardValue); return rewardValue; } function _arbitrageBellowPegDeficitOfReserves(uint256 reserveDiff, uint256 ethPrice) private returns (uint256) { uint256 uscAmountToBurn; uint256 uscAmountToFreeze; uint256 ethAmountToRedeem; uint256 ethAmountFromChi; uint256 deltaETH = _calculateDeltaETH(ethPrice); uint256 deltaUsd = _convertTokenAmountToUsdValue(deltaETH, ethPrice); if (deltaUsd > reserveDiff) { ethAmountFromChi = _convertUsdValueToTokenAmount(reserveDiff, ethPrice); uscAmountToBurn = _convertUsdValueToTokenAmount(reserveDiff, USC_TARGET_PRICE); uscAmountToFreeze = _convertUsdValueToTokenAmount(deltaUsd - reserveDiff, USC_TARGET_PRICE); ethAmountToRedeem = deltaETH - ethAmountFromChi; reserveHolder.redeem(ethAmountToRedeem); } else { ethAmountFromChi = deltaETH; uscAmountToBurn = _convertUsdValueToTokenAmount(deltaUsd, USC_TARGET_PRICE); } uint256 uscAmountReceived; { if (ethAmountFromChi > 0) { uint256 chiAmountToMint = _getAmountInForAmountOut(address(CHI), WETH, ethAmountFromChi); CHI.mint(address(this), chiAmountToMint); _swap(address(CHI), WETH, chiAmountToMint); } uscAmountReceived = _swap(WETH, address(USC), ethAmountFromChi + ethAmountToRedeem); } if (uscAmountToFreeze > 0) { rewardController.rewardUSC(uscAmountToFreeze); } if (uscAmountToBurn > 0) { USC.burn(uscAmountToBurn); } { uint256 rewardAmount = uscAmountReceived - uscAmountToFreeze - uscAmountToBurn; USC.safeTransfer(msg.sender, rewardAmount); uint256 rewardValue = _convertTokenAmountToUsdValue(rewardAmount, USC_TARGET_PRICE); emit ExecuteArbitrage(msg.sender, 4, deltaUsd, reserveDiff, ethPrice, rewardValue); return rewardValue; } } function _arbitrageAtPegExcessOfReserves( uint256 reserveDiff, uint256 discount, uint256 ethPrice ) private returns (uint256) { uint256 ethToRedeem = _convertUsdValueToTokenAmount(reserveDiff, ethPrice); reserveHolder.redeem(ethToRedeem); uint256 chiReceived = _swap(WETH, address(CHI), ethToRedeem); uint256 chiArbitrageReward = Math.mulDiv(chiReceived, discount, BASE_PRICE); CHI.burn(chiReceived - chiArbitrageReward); IERC20(CHI).safeTransfer(msg.sender, chiArbitrageReward); uint256 chiPrice = priceFeedAggregator.peek(address(CHI)); uint256 rewardValue = _convertTokenAmountToUsdValue(chiArbitrageReward, chiPrice); emit ExecuteArbitrage(msg.sender, 5, 0, reserveDiff, ethPrice, rewardValue); return rewardValue; } function _arbitrageAtPegDeficitOfReserves( uint256 reserveDiff, uint256 discount, uint256 ethPrice ) private returns (uint256) { uint256 ethToGet = _convertUsdValueToTokenAmount(reserveDiff, ethPrice); uint256 chiToCoverEth = _getAmountInForAmountOut(address(CHI), WETH, ethToGet); uint256 chiArbitrageReward = Math.mulDiv(chiToCoverEth, discount, BASE_PRICE); CHI.mint(address(this), chiToCoverEth + chiArbitrageReward); uint256 ethReceived = _swap(address(CHI), WETH, chiToCoverEth); IERC20(WETH).safeTransfer(address(reserveHolder), ethReceived); IERC20(CHI).safeTransfer(msg.sender, chiArbitrageReward); uint256 chiPrice = priceFeedAggregator.peek(address(CHI)); uint256 rewardValue = _convertTokenAmountToUsdValue(chiArbitrageReward, chiPrice); emit ExecuteArbitrage(msg.sender, 6, 0, reserveDiff, ethPrice, rewardValue); return rewardValue; } function _getReservesData() public view returns (bool isExcessOfReserves, uint256 reserveDiff, uint256 reserveValue) { reserveValue = reserveHolder.getReserveValue(); uint256 uscTotalSupplyValue = _convertTokenAmountToUsdValue(USC.totalSupply(), USC_TARGET_PRICE); if (reserveValue > uscTotalSupplyValue) { isExcessOfReserves = true; reserveDiff = (reserveValue - uscTotalSupplyValue); } else { isExcessOfReserves = false; reserveDiff = (uscTotalSupplyValue - reserveValue); } } function _getAndValidateUscPrice(uint256 ethPrice) private view returns (uint256) { uint256 uscPrice = priceFeedAggregator.peek(address(USC)); uint256 uscSpotPrice = _calculateUscSpotPrice(ethPrice); uint256 priceDiff = _absDiff(uscSpotPrice, uscPrice); uint256 maxPriceDiff = Math.mulDiv(uscPrice, priceTolerance, MAX_PRICE_TOLERANCE); if (priceDiff > maxPriceDiff) { revert PriceSlippageTooBig(); } return uscSpotPrice; } // input ethPrice has 8 decimals // returns result with 8 decimals function _calculateUscSpotPrice(uint256 ethPrice) private view returns (uint256) { (uint256 reserveUSC, uint256 reserveWETH) = UniswapV2Library.getReserves( address(poolFactory), address(USC), address(WETH) ); uint256 uscFor1ETH = UniswapV2Library.quote(1 ether, reserveWETH, reserveUSC); return Math.mulDiv(ethPrice, 1 ether, uscFor1ETH); } // what amount of In tokens to put in pool to make price: 1 tokenOut = (priceOut / priceIn) tokenIn // assuming reserves are on 18 decimals, prices are on 8 decimals function _calculateDelta( uint256 reserveIn, uint256 priceIn, uint256 reserveOut, uint256 priceOut ) public pure returns (uint256) { // F = (1 - pool_fee) = 0.997 on 18 decimals, in square root formula a = F // parameter `b` in square root formula, b = rIn * (1+f) , on 18 decimals uint256 b = Math.mulDiv(reserveIn, 1e18 + F, 1e18); uint256 b_sqr = Math.mulDiv(b, b, 1e18); // parameter `c` in square root formula, c = rIn^2 - (rIn * rOut * priceOut) / priceIn uint256 c_1 = Math.mulDiv(reserveIn, reserveIn, 1e18); uint256 c_2 = Math.mulDiv(Math.mulDiv(reserveIn, reserveOut, 1e18), priceOut, priceIn); uint256 c; uint256 root; if (c_1 > c_2) { c = c_1 - c_2; // d = 4ac uint256 d = Math.mulDiv(4 * F, c, 1e18); // root = sqrt(b^2 - 4ac) // multiplying by 10^9 to get back to 18 decimals root = Math.sqrt(b_sqr - d) * 1e9; } else { c = c_2 - c_1; // d = 4ac uint256 d = Math.mulDiv(4 * F, c, 1e18); // root = sqrt(b^2 - 4ac) -> in this case `c` is negative, so we add `d` to `b^2` // multiplying by 10^9 to get back to 18 decimals root = Math.sqrt(b_sqr + d) * 1e9; } // delta = (-b + root) / 2*f uint256 delta = Math.mulDiv(1e18, root - b, 2 * F); return delta; } // given ethPrice is on 8 decimals // how many USC to put in pool to make price: 1 ETH = ethPrice * USC function _calculateDeltaUSC(uint256 ethPrice) public view returns (uint256) { (uint256 reserveUSC, uint256 reserveWETH) = UniswapV2Library.getReserves( address(poolFactory), address(USC), address(WETH) ); return _calculateDelta(reserveUSC, USC_TARGET_PRICE, reserveWETH, ethPrice); } // how many ETH to put in pool to make price: 1 ETH = ethPrice * USC function _calculateDeltaETH(uint256 ethPrice) public view returns (uint256) { (uint256 reserveUSC, uint256 reserveWETH) = UniswapV2Library.getReserves( address(poolFactory), address(USC), address(WETH) ); return _calculateDelta(reserveWETH, ethPrice, reserveUSC, USC_TARGET_PRICE); } function _makePath(address t1, address t2) internal pure returns (address[] memory path) { path = new address[](2); path[0] = t1; path[1] = t2; } function _makePath(address t1, address t2, address t3) internal pure returns (address[] memory path) { path = new address[](3); path[0] = t1; path[1] = t2; path[2] = t3; } function _swap(address tokenIn, address tokenOut, uint256 amount) private returns (uint256) { address[] memory path; if (tokenIn != WETH && tokenOut != WETH) { path = _makePath(tokenIn, WETH, tokenOut); } else { path = _makePath(tokenIn, tokenOut); } IERC20(tokenIn).approve(address(swapRouter), amount); uint256[] memory amounts = swapRouter.swapExactTokensForTokens(amount, 0, path, address(this), block.timestamp); uint256 amountReceived = amounts[path.length - 1]; return amountReceived; } function _getAmountInForAmountOut(address tIn, address tOut, uint256 amountOut) internal view returns (uint256) { (uint256 rIn, uint256 rOut) = UniswapV2Library.getReserves(address(poolFactory), tIn, tOut); return UniswapV2Library.getAmountIn(amountOut, rIn, rOut); } function _convertUsdValueToTokenAmount(uint256 usdValue, uint256 price) internal pure returns (uint256) { return Math.mulDiv(usdValue, 1e18, price); } function _convertTokenAmountToUsdValue(uint256 amount, uint256 price) internal pure returns (uint256) { return Math.mulDiv(amount, price, 1e18); } function _absDiff(uint256 a, uint256 b) internal pure returns (uint256) { return (a > b) ? a - b : b - a; } function _almostEqualAbs(uint256 price1, uint256 price2, uint256 delta) internal pure returns (bool) { return _absDiff(price1, price2) <= delta; } receive() external payable {} }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/utils/math/Math.sol"; import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; import "@uniswap/v2-core/contracts/interfaces/IUniswapV2Factory.sol"; import "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol"; import "@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol"; import "contracts/interfaces/IWETH.sol"; import "contracts/interfaces/IPriceFeedAggregator.sol"; import "contracts/interfaces/IReserveHolder.sol"; import "contracts/interfaces/IArbitrageERC20.sol"; import "contracts/interfaces/IRewardController.sol"; import "contracts/library/ExternalContractAddresses.sol"; import "contracts/uniswap/libraries/UniswapV2Library.sol"; import "contracts/interfaces/IArbitrageV2.sol"; /// @title Contract for performing arbitrage /// @notice This contract is responsible for buying USC tokens and for keeping price of USC token pegged to target price /// @dev This contract represent second version of arbitrage contract, arbitrage is now private and all profit from arbitrage is kept in this contract contract ArbitrageV2 is IArbitrageV2, ReentrancyGuard, Ownable { using SafeERC20 for IERC20; using SafeERC20 for IArbitrageERC20; uint256 public constant BASE_PRICE = 1e8; uint256 public constant USC_TARGET_PRICE = 1e8; uint256 public constant EQ_PRICE_DELTA = 1000 wei; uint256 public constant POOL_FEE = 30; uint256 public constant MAX_FEE = 100_00; // F = (1 - pool_fee) on 18 decimals uint256 public constant F = ((MAX_FEE - POOL_FEE) * 1e18) / MAX_FEE; uint16 public constant MAX_PRICE_TOLERANCE = 100_00; address public constant WETH = ExternalContractAddresses.WETH; address public constant STETH = ExternalContractAddresses.stETH; IUniswapV2Router02 public constant swapRouter = IUniswapV2Router02(ExternalContractAddresses.UNI_V2_SWAP_ROUTER); IUniswapV2Factory public constant poolFactory = IUniswapV2Factory(ExternalContractAddresses.UNI_V2_POOL_FACTORY); IArbitrageERC20 public immutable USC; IArbitrageERC20 public immutable CHI; IRewardController public immutable rewardController; IPriceFeedAggregator public immutable priceFeedAggregator; IReserveHolder public immutable reserveHolder; uint256 public maxMintPriceDiff; uint16 public priceTolerance; mapping(address => bool) public isArbitrager; modifier onlyArbitrager() { if (!isArbitrager[msg.sender]) { revert NotArbitrager(msg.sender); } _; } constructor( IArbitrageERC20 _USC, IArbitrageERC20 _CHI, IRewardController _rewardController, IPriceFeedAggregator _priceFeedAggregator, IReserveHolder _reserveHolder ) Ownable() { USC = _USC; CHI = _CHI; rewardController = _rewardController; priceFeedAggregator = _priceFeedAggregator; reserveHolder = _reserveHolder; IERC20(USC).approve(address(rewardController), type(uint256).max); IERC20(STETH).approve(address(reserveHolder), type(uint256).max); } /// @inheritdoc IArbitrage function setPriceTolerance(uint16 _priceTolerance) external onlyOwner { if (_priceTolerance > MAX_PRICE_TOLERANCE) { revert ToleranceTooBig(_priceTolerance); } priceTolerance = _priceTolerance; emit SetPriceTolerance(_priceTolerance); } /// @inheritdoc IArbitrage function setMaxMintPriceDiff(uint256 _maxMintPriceDiff) external onlyOwner { maxMintPriceDiff = _maxMintPriceDiff; emit SetMaxMintPriceDiff(_maxMintPriceDiff); } /// @inheritdoc IArbitrageV2 function updateArbitrager(address arbitrager, bool status) external onlyOwner { isArbitrager[arbitrager] = status; emit UpdateArbitrager(arbitrager, status); } /// @inheritdoc IArbitrageV2 function claimRewards(IERC20[] memory tokens) external onlyOwner { for (uint256 i = 0; i < tokens.length; i++) { IERC20 token = tokens[i]; uint256 balance = token.balanceOf(address(this)); token.safeTransfer(msg.sender, balance); } } /// @inheritdoc IArbitrage function mint() external payable nonReentrant returns (uint256) { uint256 ethAmount = msg.value; IWETH(WETH).deposit{value: ethAmount}(); IERC20(WETH).safeTransfer(address(reserveHolder), ethAmount); return _mint(ethAmount, WETH); } /// @inheritdoc IArbitrage function mintWithWETH(uint256 wethAmount) external nonReentrant returns (uint256) { IERC20(WETH).safeTransferFrom(msg.sender, address(reserveHolder), wethAmount); return _mint(wethAmount, WETH); } /// @inheritdoc IArbitrage function mintWithStETH(uint256 stETHAmount) external nonReentrant returns (uint256) { IERC20(STETH).safeTransferFrom(msg.sender, address(this), stETHAmount); reserveHolder.deposit(IERC20(STETH).balanceOf(address(this))); return _mint(stETHAmount, STETH); } /// @inheritdoc IArbitrage function executeArbitrage() public override nonReentrant onlyArbitrager returns (uint256) { return _executeArbitrage(); } /// @inheritdoc IArbitrage function getArbitrageData() public view returns (bool isPriceAboveTarget, bool isExcessOfReserves, uint256 reserveDiff, uint256 discount) { uint256 ethPrice = priceFeedAggregator.peek(WETH); uint256 uscPrice = _getAndValidateUscPrice(ethPrice); uint256 reserveValue; (isExcessOfReserves, reserveDiff, reserveValue) = _getReservesData(); isPriceAboveTarget = uscPrice >= USC_TARGET_PRICE; //If prices are equal delta does not need to be calculated if (_almostEqualAbs(uscPrice, USC_TARGET_PRICE, EQ_PRICE_DELTA)) { discount = Math.mulDiv(reserveDiff, BASE_PRICE, reserveValue); } } function _executeArbitrage() internal returns (uint256) { (bool isPriceAboveTarget, bool isExcessOfReserves, uint256 reserveDiff, uint256 discount) = getArbitrageData(); uint256 ethPrice = priceFeedAggregator.peek(WETH); if (discount != 0) { if (isExcessOfReserves) { return _arbitrageAtPegExcessOfReserves(reserveDiff, discount, ethPrice); } else { return _arbitrageAtPegDeficitOfReserves(reserveDiff, discount, ethPrice); } } else if (isPriceAboveTarget) { if (isExcessOfReserves) { return _arbitrageAbovePegExcessOfReserves(reserveDiff, ethPrice); } else { return _arbitrageAbovePegDeficitOfReserves(reserveDiff, ethPrice); } } else { if (isExcessOfReserves) { return _arbitrageBellowPegExcessOfReserves(reserveDiff, ethPrice); } else { return _arbitrageBellowPegDeficitOfReserves(reserveDiff, ethPrice); } } } function _mint(uint256 amount, address token) private returns (uint256) { uint256 ethPrice = priceFeedAggregator.peek(WETH); uint256 uscSpotPrice = _calculateUscSpotPrice(ethPrice); if (!_almostEqualAbs(uscSpotPrice, USC_TARGET_PRICE, maxMintPriceDiff)) { revert PriceIsNotPegged(); } uint256 usdValue; if (token == WETH) { usdValue = _convertTokenAmountToUsdValue(amount, ethPrice); } else { uint256 tokenPrice = priceFeedAggregator.peek(token); usdValue = _convertTokenAmountToUsdValue(amount, tokenPrice); } uint256 uscAmountToMint = _convertUsdValueToTokenAmount(usdValue, USC_TARGET_PRICE); USC.mint(msg.sender, uscAmountToMint); emit Mint(msg.sender, token, amount, uscAmountToMint); return uscAmountToMint; } function _arbitrageAbovePegExcessOfReserves(uint256 reserveDiff, uint256 ethPrice) private returns (uint256) { uint256 deltaUSC = _calculateDeltaUSC(ethPrice); USC.mint(address(this), deltaUSC); uint256 ethAmountReceived = _swap(address(USC), WETH, deltaUSC); uint256 deltaUsd = _convertTokenAmountToUsdValue(deltaUSC, USC_TARGET_PRICE); uint256 deltaInETH = _convertUsdValueToTokenAmount(deltaUsd, ethPrice); if (deltaInETH > ethAmountReceived) { revert DeltaBiggerThanAmountReceivedETH(deltaInETH, ethAmountReceived); } uint256 ethAmountToSwap; uint256 ethAmountForReserves; if (deltaUsd > reserveDiff) { ethAmountToSwap = _convertUsdValueToTokenAmount(reserveDiff, ethPrice); ethAmountForReserves = _convertUsdValueToTokenAmount(deltaUsd, ethPrice) - ethAmountToSwap; IERC20(WETH).safeTransfer(address(reserveHolder), ethAmountForReserves); } else { ethAmountToSwap = _convertUsdValueToTokenAmount(deltaUsd, ethPrice); } uint256 chiAmountReceived = _swap(WETH, address(CHI), ethAmountToSwap); CHI.burn(chiAmountReceived); uint256 rewardAmount = ethAmountReceived - ethAmountToSwap - ethAmountForReserves; uint256 rewardValue = _convertTokenAmountToUsdValue(rewardAmount, ethPrice); emit ExecuteArbitrage(msg.sender, 1, deltaUsd, reserveDiff, ethPrice, rewardValue); return rewardValue; } function _arbitrageAbovePegDeficitOfReserves(uint256 reserveDiff, uint256 ethPrice) private returns (uint256) { uint256 deltaUSC = _calculateDeltaUSC(ethPrice); USC.mint(address(this), deltaUSC); uint256 ethAmountReceived = _swap(address(USC), WETH, deltaUSC); uint256 deltaUsd = _convertTokenAmountToUsdValue(deltaUSC, USC_TARGET_PRICE); uint256 deltaInETH = _convertUsdValueToTokenAmount(deltaUsd, ethPrice); if (deltaInETH > ethAmountReceived) { revert DeltaBiggerThanAmountReceivedETH(deltaInETH, ethAmountReceived); } IERC20(WETH).safeTransfer(address(reserveHolder), deltaInETH); uint256 rewardAmount = ethAmountReceived - deltaInETH; uint256 rewardValue = _convertTokenAmountToUsdValue(rewardAmount, ethPrice); emit ExecuteArbitrage(msg.sender, 2, deltaUsd, reserveDiff, ethPrice, rewardValue); return rewardValue; } function _arbitrageBellowPegExcessOfReserves(uint256 reserveDiff, uint256 ethPrice) private returns (uint256) { uint256 uscAmountToFreeze; uint256 uscAmountToBurn; uint256 ethAmountToRedeem; uint256 ethAmountFromChi; uint256 deltaETH = _calculateDeltaETH(ethPrice); uint256 deltaUsd = _convertTokenAmountToUsdValue(deltaETH, ethPrice); if (deltaUsd > reserveDiff) { ethAmountToRedeem = _convertUsdValueToTokenAmount(reserveDiff, ethPrice); uscAmountToFreeze = _convertUsdValueToTokenAmount(reserveDiff, USC_TARGET_PRICE); uscAmountToBurn = _convertUsdValueToTokenAmount(deltaUsd - reserveDiff, USC_TARGET_PRICE); uint256 chiAmountToMint = _getAmountInForAmountOut(address(CHI), WETH, deltaETH - ethAmountToRedeem); CHI.mint(address(this), chiAmountToMint); ethAmountFromChi = _swap(address(CHI), WETH, chiAmountToMint); } else { ethAmountToRedeem = deltaETH; uscAmountToFreeze = _convertUsdValueToTokenAmount(deltaUsd, USC_TARGET_PRICE); } reserveHolder.redeem(ethAmountToRedeem); uint256 uscAmountReceived = _swap(WETH, address(USC), ethAmountToRedeem + ethAmountFromChi); if (uscAmountReceived < uscAmountToFreeze) { uscAmountToFreeze = uscAmountReceived; } rewardController.rewardUSC(uscAmountToFreeze); if (uscAmountToBurn > 0) { USC.burn(uscAmountToBurn); } uint256 rewardAmount = uscAmountReceived - uscAmountToFreeze - uscAmountToBurn; uint256 rewardValue = _convertTokenAmountToUsdValue(rewardAmount, USC_TARGET_PRICE); emit ExecuteArbitrage(msg.sender, 3, deltaUsd, reserveDiff, ethPrice, rewardValue); return rewardValue; } function _arbitrageBellowPegDeficitOfReserves(uint256 reserveDiff, uint256 ethPrice) private returns (uint256) { uint256 uscAmountToBurn; uint256 uscAmountToFreeze; uint256 ethAmountToRedeem; uint256 ethAmountFromChi; uint256 deltaETH = _calculateDeltaETH(ethPrice); uint256 deltaUsd = _convertTokenAmountToUsdValue(deltaETH, ethPrice); if (deltaUsd > reserveDiff) { ethAmountFromChi = _convertUsdValueToTokenAmount(reserveDiff, ethPrice); uscAmountToBurn = _convertUsdValueToTokenAmount(reserveDiff, USC_TARGET_PRICE); uscAmountToFreeze = _convertUsdValueToTokenAmount(deltaUsd - reserveDiff, USC_TARGET_PRICE); ethAmountToRedeem = deltaETH - ethAmountFromChi; reserveHolder.redeem(ethAmountToRedeem); } else { ethAmountFromChi = deltaETH; uscAmountToBurn = _convertUsdValueToTokenAmount(deltaUsd, USC_TARGET_PRICE); } uint256 uscAmountReceived; { if (ethAmountFromChi > 0) { uint256 chiAmountToMint = _getAmountInForAmountOut(address(CHI), WETH, ethAmountFromChi); CHI.mint(address(this), chiAmountToMint); _swap(address(CHI), WETH, chiAmountToMint); } uscAmountReceived = _swap(WETH, address(USC), ethAmountFromChi + ethAmountToRedeem); } if (uscAmountToFreeze > 0) { rewardController.rewardUSC(uscAmountToFreeze); } if (uscAmountToBurn > 0) { USC.burn(uscAmountToBurn); } { uint256 rewardAmount = uscAmountReceived - uscAmountToFreeze - uscAmountToBurn; uint256 rewardValue = _convertTokenAmountToUsdValue(rewardAmount, USC_TARGET_PRICE); emit ExecuteArbitrage(msg.sender, 4, deltaUsd, reserveDiff, ethPrice, rewardValue); return rewardValue; } } function _arbitrageAtPegExcessOfReserves( uint256 reserveDiff, uint256 discount, uint256 ethPrice ) private returns (uint256) { uint256 ethToRedeem = _convertUsdValueToTokenAmount(reserveDiff, ethPrice); reserveHolder.redeem(ethToRedeem); uint256 chiReceived = _swap(WETH, address(CHI), ethToRedeem); uint256 chiArbitrageReward = Math.mulDiv(chiReceived, discount, BASE_PRICE); CHI.burn(chiReceived - chiArbitrageReward); uint256 chiPrice = priceFeedAggregator.peek(address(CHI)); uint256 rewardValue = _convertTokenAmountToUsdValue(chiArbitrageReward, chiPrice); emit ExecuteArbitrage(msg.sender, 5, 0, reserveDiff, ethPrice, rewardValue); return rewardValue; } function _arbitrageAtPegDeficitOfReserves( uint256 reserveDiff, uint256 discount, uint256 ethPrice ) private returns (uint256) { uint256 ethToGet = _convertUsdValueToTokenAmount(reserveDiff, ethPrice); uint256 chiToCoverEth = _getAmountInForAmountOut(address(CHI), WETH, ethToGet); uint256 chiArbitrageReward = Math.mulDiv(chiToCoverEth, discount, BASE_PRICE); CHI.mint(address(this), chiToCoverEth + chiArbitrageReward); uint256 ethReceived = _swap(address(CHI), WETH, chiToCoverEth); IERC20(WETH).safeTransfer(address(reserveHolder), ethReceived); uint256 chiPrice = priceFeedAggregator.peek(address(CHI)); uint256 rewardValue = _convertTokenAmountToUsdValue(chiArbitrageReward, chiPrice); emit ExecuteArbitrage(msg.sender, 6, 0, reserveDiff, ethPrice, rewardValue); return rewardValue; } function _getReservesData() public view returns (bool isExcessOfReserves, uint256 reserveDiff, uint256 reserveValue) { reserveValue = reserveHolder.getReserveValue(); uint256 uscTotalSupplyValue = _convertTokenAmountToUsdValue(USC.totalSupply(), USC_TARGET_PRICE); if (reserveValue > uscTotalSupplyValue) { isExcessOfReserves = true; reserveDiff = (reserveValue - uscTotalSupplyValue); } else { isExcessOfReserves = false; reserveDiff = (uscTotalSupplyValue - reserveValue); } } function _getAndValidateUscPrice(uint256 ethPrice) private view returns (uint256) { uint256 uscPrice = priceFeedAggregator.peek(address(USC)); uint256 uscSpotPrice = _calculateUscSpotPrice(ethPrice); uint256 priceDiff = _absDiff(uscSpotPrice, uscPrice); uint256 maxPriceDiff = Math.mulDiv(uscPrice, priceTolerance, MAX_PRICE_TOLERANCE); if (priceDiff > maxPriceDiff) { revert PriceSlippageTooBig(); } return uscSpotPrice; } // input ethPrice has 8 decimals // returns result with 8 decimals function _calculateUscSpotPrice(uint256 ethPrice) private view returns (uint256) { (uint256 reserveUSC, uint256 reserveWETH) = UniswapV2Library.getReserves( address(poolFactory), address(USC), address(WETH) ); uint256 uscFor1ETH = UniswapV2Library.quote(1 ether, reserveWETH, reserveUSC); return Math.mulDiv(ethPrice, 1 ether, uscFor1ETH); } // what amount of In tokens to put in pool to make price: 1 tokenOut = (priceOut / priceIn) tokenIn // assuming reserves are on 18 decimals, prices are on 8 decimals function _calculateDelta( uint256 reserveIn, uint256 priceIn, uint256 reserveOut, uint256 priceOut ) public pure returns (uint256) { // F = (1 - pool_fee) = 0.997 on 18 decimals, in square root formula a = F // parameter `b` in square root formula, b = rIn * (1+f) , on 18 decimals uint256 b = Math.mulDiv(reserveIn, 1e18 + F, 1e18); uint256 b_sqr = Math.mulDiv(b, b, 1e18); // parameter `c` in square root formula, c = rIn^2 - (rIn * rOut * priceOut) / priceIn uint256 c_1 = Math.mulDiv(reserveIn, reserveIn, 1e18); uint256 c_2 = Math.mulDiv(Math.mulDiv(reserveIn, reserveOut, 1e18), priceOut, priceIn); uint256 c; uint256 root; if (c_1 > c_2) { c = c_1 - c_2; // d = 4ac uint256 d = Math.mulDiv(4 * F, c, 1e18); // root = sqrt(b^2 - 4ac) // multiplying by 10^9 to get back to 18 decimals root = Math.sqrt(b_sqr - d) * 1e9; } else { c = c_2 - c_1; // d = 4ac uint256 d = Math.mulDiv(4 * F, c, 1e18); // root = sqrt(b^2 - 4ac) -> in this case `c` is negative, so we add `d` to `b^2` // multiplying by 10^9 to get back to 18 decimals root = Math.sqrt(b_sqr + d) * 1e9; } // delta = (-b + root) / 2*f uint256 delta = Math.mulDiv(1e18, root - b, 2 * F); return delta; } // given ethPrice is on 8 decimals // how many USC to put in pool to make price: 1 ETH = ethPrice * USC function _calculateDeltaUSC(uint256 ethPrice) public view returns (uint256) { (uint256 reserveUSC, uint256 reserveWETH) = UniswapV2Library.getReserves( address(poolFactory), address(USC), address(WETH) ); return _calculateDelta(reserveUSC, USC_TARGET_PRICE, reserveWETH, ethPrice); } // how many ETH to put in pool to make price: 1 ETH = ethPrice * USC function _calculateDeltaETH(uint256 ethPrice) public view returns (uint256) { (uint256 reserveUSC, uint256 reserveWETH) = UniswapV2Library.getReserves( address(poolFactory), address(USC), address(WETH) ); return _calculateDelta(reserveWETH, ethPrice, reserveUSC, USC_TARGET_PRICE); } function _makePath(address t1, address t2) internal pure returns (address[] memory path) { path = new address[](2); path[0] = t1; path[1] = t2; } function _makePath(address t1, address t2, address t3) internal pure returns (address[] memory path) { path = new address[](3); path[0] = t1; path[1] = t2; path[2] = t3; } function _swap(address tokenIn, address tokenOut, uint256 amount) private returns (uint256) { address[] memory path; if (tokenIn != WETH && tokenOut != WETH) { path = _makePath(tokenIn, WETH, tokenOut); } else { path = _makePath(tokenIn, tokenOut); } IERC20(tokenIn).approve(address(swapRouter), amount); uint256[] memory amounts = swapRouter.swapExactTokensForTokens(amount, 0, path, address(this), block.timestamp); uint256 amountReceived = amounts[path.length - 1]; return amountReceived; } function _getAmountInForAmountOut(address tIn, address tOut, uint256 amountOut) internal view returns (uint256) { (uint256 rIn, uint256 rOut) = UniswapV2Library.getReserves(address(poolFactory), tIn, tOut); return UniswapV2Library.getAmountIn(amountOut, rIn, rOut); } function _convertUsdValueToTokenAmount(uint256 usdValue, uint256 price) internal pure returns (uint256) { return Math.mulDiv(usdValue, 1e18, price); } function _convertTokenAmountToUsdValue(uint256 amount, uint256 price) internal pure returns (uint256) { return Math.mulDiv(amount, price, 1e18); } function _absDiff(uint256 a, uint256 b) internal pure returns (uint256) { return (a > b) ? a - b : b - a; } function _almostEqualAbs(uint256 price1, uint256 price2, uint256 delta) internal pure returns (bool) { return _absDiff(price1, price2) <= delta; } receive() external payable {} }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/utils/math/Math.sol"; import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; import "@uniswap/v2-core/contracts/interfaces/IUniswapV2Factory.sol"; import "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol"; import "@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol"; import "contracts/interfaces/IWETH.sol"; import "contracts/interfaces/IPriceFeedAggregator.sol"; import "contracts/interfaces/IReserveHolder.sol"; import "contracts/interfaces/IArbitrageERC20.sol"; import "contracts/interfaces/IRewardController.sol"; import "contracts/library/ExternalContractAddresses.sol"; import "contracts/uniswap/libraries/UniswapV2Library.sol"; import "contracts/interfaces/IArbitrageV3.sol"; /// @title Contract for performing arbitrage /// @notice This contract is responsible for buying USC tokens and for keeping price of USC token pegged to target price /// @dev This contract represent second version of arbitrage contract, arbitrage is now private and all profit from arbitrage is kept in this contract contract ArbitrageV4 is IArbitrageV3, ReentrancyGuard, Ownable { using SafeERC20 for IERC20; using SafeERC20 for IArbitrageERC20; uint256 public constant BASE_PRICE = 1e8; uint256 public constant USC_TARGET_PRICE = 1e8; uint256 public constant POOL_FEE = 30; uint256 public constant MAX_FEE = 100_00; // F = (1 - pool_fee) on 18 decimals uint256 public constant F = ((MAX_FEE - POOL_FEE) * 1e18) / MAX_FEE; uint16 public constant MAX_PRICE_TOLERANCE = 100_00; address public constant WETH = ExternalContractAddresses.WETH; address public constant STETH = ExternalContractAddresses.stETH; IUniswapV2Router02 public constant swapRouter = IUniswapV2Router02(ExternalContractAddresses.UNI_V2_SWAP_ROUTER); IUniswapV2Factory public constant poolFactory = IUniswapV2Factory(ExternalContractAddresses.UNI_V2_POOL_FACTORY); IArbitrageERC20 public immutable USC; IArbitrageERC20 public immutable CHI; IRewardController public immutable rewardController; IPriceFeedAggregator public immutable priceFeedAggregator; IReserveHolder public immutable reserveHolder; uint256 public pegPriceToleranceAbs; uint256 public mintBurnFee; uint256 public maxMintBurnPriceDiff; uint16 public maxMintBurnReserveTolerance; uint16 public chiPriceTolerance; uint16 public priceTolerance; bool public mintPaused; bool public burnPaused; uint256 public totalMintedUsc; // total amount of minted USC tokens during arbitrage when stablecoin is pegged mapping(address => bool) public isArbitrager; mapping(address => bool) public isPrivileged; modifier onlyArbitrager() { if (!isArbitrager[msg.sender]) { revert NotArbitrager(msg.sender); } _; } modifier whenMintNotPaused() { if (mintPaused) { revert ContractIsPaused(); } _; } modifier whenBurnNotPaused() { if (burnPaused) { revert ContractIsPaused(); } _; } modifier onlyWhenMintableOrBurnable() { uint256 ethPrice = priceFeedAggregator.peek(WETH); uint256 uscSpotPrice = _calculateUscSpotPrice(ethPrice); if (!_almostEqualAbs(uscSpotPrice, USC_TARGET_PRICE, maxMintBurnPriceDiff)) { revert PriceIsNotPegged(); } (, uint256 reserveDiff, uint256 reserveValue) = _getReservesData(); if (reserveDiff > Math.mulDiv(reserveValue, maxMintBurnReserveTolerance, MAX_PRICE_TOLERANCE)) { revert ReserveDiffTooBig(); } _; } constructor( IArbitrageERC20 _USC, IArbitrageERC20 _CHI, IRewardController _rewardController, IPriceFeedAggregator _priceFeedAggregator, IReserveHolder _reserveHolder ) Ownable() { USC = _USC; CHI = _CHI; rewardController = _rewardController; priceFeedAggregator = _priceFeedAggregator; reserveHolder = _reserveHolder; mintPaused = false; burnPaused = false; IERC20(USC).approve(address(rewardController), type(uint256).max); IERC20(STETH).approve(address(reserveHolder), type(uint256).max); } /// @inheritdoc IArbitrageV3 function setPegPriceToleranceAbs(uint256 _priceTolerance) external override onlyOwner { pegPriceToleranceAbs = _priceTolerance; } /// @inheritdoc IArbitrageV3 function setMintPause(bool isPaused) external onlyOwner { mintPaused = isPaused; } /// @inheritdoc IArbitrageV3 function setBurnPause(bool isPaused) external onlyOwner { burnPaused = isPaused; } /// @inheritdoc IArbitrageV3 function setPriceTolerance(uint16 _priceTolerance) external onlyOwner { if (_priceTolerance > MAX_PRICE_TOLERANCE) { revert ToleranceTooBig(_priceTolerance); } priceTolerance = _priceTolerance; emit SetPriceTolerance(_priceTolerance); } /// @inheritdoc IArbitrageV3 function setChiPriceTolerance(uint16 _chiPriceTolerance) external onlyOwner { if (_chiPriceTolerance > MAX_PRICE_TOLERANCE) { revert ToleranceTooBig(_chiPriceTolerance); } chiPriceTolerance = _chiPriceTolerance; emit SetChiPriceTolerance(_chiPriceTolerance); } /// @inheritdoc IArbitrageV3 function setMaxMintBurnPriceDiff(uint256 _maxMintBurnPriceDiff) external onlyOwner { maxMintBurnPriceDiff = _maxMintBurnPriceDiff; emit SetMaxMintBurnPriceDiff(_maxMintBurnPriceDiff); } /// @inheritdoc IArbitrageV3 function setMaxMintBurnReserveTolerance(uint16 _maxMintBurnReserveTolerance) external onlyOwner { if (_maxMintBurnReserveTolerance > MAX_PRICE_TOLERANCE) { revert ToleranceTooBig(_maxMintBurnReserveTolerance); } maxMintBurnReserveTolerance = _maxMintBurnReserveTolerance; emit SetMaxMintBurnReserveTolerance(_maxMintBurnReserveTolerance); } /// @inheritdoc IArbitrageV3 function setMintBurnFee(uint16 _mintBurnFee) external onlyOwner { if (_mintBurnFee > MAX_FEE) { revert FeeTooBig(_mintBurnFee); } mintBurnFee = _mintBurnFee; emit SetMintBurnFee(_mintBurnFee); } /// @inheritdoc IArbitrageV3 function updateArbitrager(address arbitrager, bool status) external onlyOwner { isArbitrager[arbitrager] = status; emit UpdateArbitrager(arbitrager, status); } /// @inheritdoc IArbitrageV3 function updatePrivileged(address account, bool status) external onlyOwner { isPrivileged[account] = status; emit UpdatePrivileged(account, status); } /// @inheritdoc IArbitrageV3 function claimRewards(IERC20[] memory tokens, uint256[] memory amounts) external onlyOwner { for (uint256 i = 0; i < tokens.length; i++) { IERC20 token = tokens[i]; uint256 amount = amounts[i]; token.safeTransfer(msg.sender, amount); if (address(token) == address(USC)) { if (amount < totalMintedUsc) { totalMintedUsc -= amount; } else { totalMintedUsc = 0; } } } } /// @inheritdoc IArbitrageV3 function rewardUSC(uint256 amount) external onlyOwner { USC.safeTransferFrom(msg.sender, address(this), amount); totalMintedUsc += amount; emit RewardUSC(amount); } /// @inheritdoc IArbitrageV3 function mint( address receiver ) external payable whenMintNotPaused nonReentrant onlyWhenMintableOrBurnable returns (uint256) { uint256 ethAmount = msg.value; uint256 fee = Math.mulDiv(ethAmount, mintBurnFee, MAX_FEE); uint256 ethAmountAfterFee = ethAmount - fee; IWETH(WETH).deposit{value: ethAmount}(); IERC20(WETH).safeTransfer(address(reserveHolder), ethAmountAfterFee); return _mint(ethAmountAfterFee, WETH, receiver); } /// @inheritdoc IArbitrageV3 function mintWithWETH( uint256 wethAmount, address receiver ) external whenMintNotPaused nonReentrant onlyWhenMintableOrBurnable returns (uint256) { uint256 fee = Math.mulDiv(wethAmount, mintBurnFee, MAX_FEE); IERC20(WETH).safeTransferFrom(msg.sender, address(this), fee); uint256 wethAmountAfterFee = wethAmount - fee; IERC20(WETH).safeTransferFrom(msg.sender, address(reserveHolder), wethAmountAfterFee); return _mint(wethAmountAfterFee, WETH, receiver); } /// @inheritdoc IArbitrageV3 function mintWithStETH( uint256 stETHAmount, address receiver ) external whenMintNotPaused nonReentrant onlyWhenMintableOrBurnable returns (uint256) { uint256 fee = Math.mulDiv(stETHAmount, mintBurnFee, MAX_FEE); IERC20(STETH).safeTransferFrom(msg.sender, address(this), fee); uint256 stETHAmountAfterFee = stETHAmount - fee; IERC20(STETH).safeTransferFrom(msg.sender, address(this), stETHAmountAfterFee); reserveHolder.deposit(IERC20(STETH).balanceOf(address(this))); return _mint(stETHAmountAfterFee, STETH, receiver); } /// @inheritdoc IArbitrageV3 function burn( uint256 amount, address receiver ) external whenBurnNotPaused nonReentrant onlyWhenMintableOrBurnable returns (uint256) { uint256 ethPrice = priceFeedAggregator.peek(WETH); USC.safeTransferFrom(msg.sender, address(this), amount); amount -= (amount * mintBurnFee) / MAX_FEE; USC.burn(amount); uint256 ethAmountToRedeem = Math.mulDiv(amount, USC_TARGET_PRICE, ethPrice); reserveHolder.redeem(ethAmountToRedeem); IERC20(WETH).safeTransfer(receiver, ethAmountToRedeem); emit Burn(msg.sender, receiver, amount, ethAmountToRedeem); return ethAmountToRedeem; } /// @inheritdoc IArbitrageV3 function executeArbitrage(uint256 maxChiSpotPrice) public override nonReentrant onlyArbitrager returns (uint256) { _validateArbitrage(maxChiSpotPrice); return _executeArbitrage(); } /// @inheritdoc IArbitrageV3 function getArbitrageData() public view returns (bool isPriceAboveTarget, bool isExcessOfReserves, uint256 reserveDiff, uint256 discount) { uint256 ethPrice = priceFeedAggregator.peek(WETH); uint256 uscPrice = _getAndValidateUscPrice(ethPrice); uint256 reserveValue; (isExcessOfReserves, reserveDiff, reserveValue) = _getReservesData(); isPriceAboveTarget = uscPrice >= USC_TARGET_PRICE; //If prices are equal delta does not need to be calculated if (_almostEqualAbs(uscPrice, USC_TARGET_PRICE, pegPriceToleranceAbs)) { discount = Math.mulDiv(reserveDiff, BASE_PRICE, reserveValue); } } function _executeArbitrage() internal returns (uint256) { (bool isPriceAboveTarget, bool isExcessOfReserves, uint256 reserveDiff, uint256 discount) = getArbitrageData(); uint256 ethPrice = priceFeedAggregator.peek(WETH); if (discount != 0) { if (isExcessOfReserves) { return _arbitrageAtPegExcessOfReserves(reserveDiff, discount, ethPrice); } else { return _arbitrageAtPegDeficitOfReserves(reserveDiff, discount, ethPrice); } } else if (isPriceAboveTarget) { if (isExcessOfReserves) { return _arbitrageAbovePegExcessOfReserves(reserveDiff, ethPrice); } else { return _arbitrageAbovePegDeficitOfReserves(reserveDiff, ethPrice); } } else { if (isExcessOfReserves) { return _arbitrageBellowPegExcessOfReserves(reserveDiff, ethPrice); } else { return _arbitrageBellowPegDeficitOfReserves(reserveDiff, ethPrice); } } } function _mint(uint256 amount, address token, address receiver) private returns (uint256) { uint256 usdValue; if (token == WETH) { uint256 ethPrice = priceFeedAggregator.peek(WETH); usdValue = _convertTokenAmountToUsdValue(amount, ethPrice); } else { uint256 tokenPrice = priceFeedAggregator.peek(token); usdValue = _convertTokenAmountToUsdValue(amount, tokenPrice); } uint256 uscAmountToMint = _convertUsdValueToTokenAmount(usdValue, USC_TARGET_PRICE); USC.mint(receiver, uscAmountToMint); emit Mint(msg.sender, receiver, token, amount, uscAmountToMint); return uscAmountToMint; } function _arbitrageAbovePegExcessOfReserves(uint256 reserveDiff, uint256 ethPrice) private returns (uint256) { uint256 deltaUSC = _calculateDeltaUSC(ethPrice); USC.mint(address(this), deltaUSC); uint256 ethAmountReceived = _swap(address(USC), WETH, deltaUSC); uint256 deltaUsd = _convertTokenAmountToUsdValue(deltaUSC, USC_TARGET_PRICE); uint256 deltaInETH = _convertUsdValueToTokenAmount(deltaUsd, ethPrice); if (deltaInETH > ethAmountReceived) { revert DeltaBiggerThanAmountReceivedETH(deltaInETH, ethAmountReceived); } uint256 ethAmountToSwap; uint256 ethAmountForReserves; if (deltaUsd > reserveDiff) { ethAmountToSwap = _convertUsdValueToTokenAmount(reserveDiff, ethPrice); ethAmountForReserves = _convertUsdValueToTokenAmount(deltaUsd, ethPrice) - ethAmountToSwap; IERC20(WETH).safeTransfer(address(reserveHolder), ethAmountForReserves); } else { ethAmountToSwap = _convertUsdValueToTokenAmount(deltaUsd, ethPrice); } uint256 chiAmountReceived = _swap(WETH, address(CHI), ethAmountToSwap); CHI.burn(chiAmountReceived); uint256 rewardAmount = ethAmountReceived - ethAmountToSwap - ethAmountForReserves; uint256 rewardValue = _convertTokenAmountToUsdValue(rewardAmount, ethPrice); emit ExecuteArbitrage(msg.sender, 1, deltaUsd, reserveDiff, ethPrice, rewardValue); return rewardValue; } function _arbitrageAbovePegDeficitOfReserves(uint256 reserveDiff, uint256 ethPrice) private returns (uint256) { uint256 deltaUSC = _calculateDeltaUSC(ethPrice); USC.mint(address(this), deltaUSC); uint256 ethAmountReceived = _swap(address(USC), WETH, deltaUSC); uint256 deltaUsd = _convertTokenAmountToUsdValue(deltaUSC, USC_TARGET_PRICE); uint256 deltaInETH = _convertUsdValueToTokenAmount(deltaUsd, ethPrice); if (deltaInETH > ethAmountReceived) { revert DeltaBiggerThanAmountReceivedETH(deltaInETH, ethAmountReceived); } IERC20(WETH).safeTransfer(address(reserveHolder), deltaInETH); uint256 rewardAmount = ethAmountReceived - deltaInETH; uint256 rewardValue = _convertTokenAmountToUsdValue(rewardAmount, ethPrice); emit ExecuteArbitrage(msg.sender, 2, deltaUsd, reserveDiff, ethPrice, rewardValue); return rewardValue; } function _arbitrageBellowPegExcessOfReserves(uint256 reserveDiff, uint256 ethPrice) private returns (uint256) { uint256 uscAmountToFreeze; uint256 uscAmountToBurn; uint256 deltaETH = _calculateDeltaETH(ethPrice); uint256 deltaUsd = _convertTokenAmountToUsdValue(deltaETH, ethPrice); if (deltaUsd > reserveDiff) { uscAmountToFreeze = _convertUsdValueToTokenAmount(reserveDiff, USC_TARGET_PRICE); uscAmountToBurn = _convertUsdValueToTokenAmount(deltaUsd - reserveDiff, USC_TARGET_PRICE); } else { uscAmountToFreeze = _convertUsdValueToTokenAmount(deltaUsd, USC_TARGET_PRICE); } reserveHolder.redeem(deltaETH); uint256 uscAmountReceived = _swap(WETH, address(USC), deltaETH); if (uscAmountReceived < uscAmountToFreeze) { uscAmountToFreeze = uscAmountReceived; } rewardController.rewardUSC(uscAmountToFreeze); if (uscAmountToBurn > 0) { USC.burn(uscAmountToBurn); } uint256 rewardAmount = uscAmountReceived - uscAmountToFreeze - uscAmountToBurn; uint256 rewardValue = _convertTokenAmountToUsdValue(rewardAmount, USC_TARGET_PRICE); emit ExecuteArbitrage(msg.sender, 3, deltaUsd, reserveDiff, ethPrice, rewardValue); return rewardValue; } function _arbitrageBellowPegDeficitOfReserves(uint256 reserveDiff, uint256 ethPrice) private returns (uint256) { uint256 ethAmountToRedeem; uint256 ethAmountFromChi; uint256 deltaETH = _calculateDeltaETH(ethPrice); uint256 deltaUsd = _convertTokenAmountToUsdValue(deltaETH, ethPrice); if (deltaUsd > reserveDiff) { ethAmountFromChi = _convertUsdValueToTokenAmount(reserveDiff, ethPrice); ethAmountToRedeem = deltaETH - ethAmountFromChi; reserveHolder.redeem(ethAmountToRedeem); } else { ethAmountFromChi = deltaETH; } uint256 uscAmountToBurn = _convertUsdValueToTokenAmount(deltaUsd, USC_TARGET_PRICE); uint256 uscAmountReceived; { if (ethAmountFromChi > 0) { uint256 chiAmountToMint = _getAmountInForAmountOut(address(CHI), WETH, ethAmountFromChi); CHI.mint(address(this), chiAmountToMint); _swap(address(CHI), WETH, chiAmountToMint); } uscAmountReceived = _swap(WETH, address(USC), ethAmountFromChi + ethAmountToRedeem); } if (uscAmountToBurn > 0) { USC.burn(uscAmountToBurn); } { uint256 rewardAmount = uscAmountReceived - uscAmountToBurn; uint256 rewardValue = _convertTokenAmountToUsdValue(rewardAmount, USC_TARGET_PRICE); emit ExecuteArbitrage(msg.sender, 4, deltaUsd, reserveDiff, ethPrice, rewardValue); return rewardValue; } } function _arbitrageAtPegExcessOfReserves( uint256 reserveDiff, uint256 discount, uint256 ethPrice ) private returns (uint256) { uint256 uscAmountToMint = _convertUsdValueToTokenAmount(reserveDiff, USC_TARGET_PRICE); uint256 uscAmountForReward = Math.mulDiv(uscAmountToMint, discount, BASE_PRICE); USC.mint(address(this), uscAmountToMint); totalMintedUsc += uscAmountToMint - uscAmountForReward; uint256 rewardValue = _convertTokenAmountToUsdValue(uscAmountForReward, USC_TARGET_PRICE); emit ExecuteArbitrage(msg.sender, 5, 0, reserveDiff, ethPrice, rewardValue); return rewardValue; } function _arbitrageAtPegDeficitOfReserves( uint256 reserveDiff, uint256 discount, uint256 ethPrice ) private returns (uint256) { uint256 reserveDiffInUsc = _convertUsdValueToTokenAmount(reserveDiff, USC_TARGET_PRICE); uint256 uscAmountToBurn; uint256 ethToGet; if (reserveDiffInUsc > totalMintedUsc) { uscAmountToBurn = totalMintedUsc; uint256 ethToGetInUsd = _convertTokenAmountToUsdValue(reserveDiffInUsc - totalMintedUsc, USC_TARGET_PRICE); ethToGet = _convertUsdValueToTokenAmount(ethToGetInUsd, ethPrice); } else { uscAmountToBurn = reserveDiffInUsc; ethToGet = 0; } uint256 chiAmountForRewardInUsd = Math.mulDiv(reserveDiff, discount, BASE_PRICE); uint256 chiPrice = priceFeedAggregator.peek(address(CHI)); uint256 chiAmountForReward = _convertUsdValueToTokenAmount(chiAmountForRewardInUsd, chiPrice); uint256 chiToCoverEth; if (ethToGet > 0) { chiToCoverEth = _getAmountInForAmountOut(address(CHI), WETH, ethToGet); } CHI.mint(address(this), chiToCoverEth + chiAmountForReward); if (ethToGet > 0) { uint256 ethReceived = _swap(address(CHI), WETH, chiToCoverEth); IERC20(WETH).safeTransfer(address(reserveHolder), ethReceived); } if (uscAmountToBurn > 0) { USC.burn(uscAmountToBurn); totalMintedUsc -= uscAmountToBurn; } emit ExecuteArbitrage(msg.sender, 6, 0, reserveDiff, ethPrice, chiAmountForRewardInUsd); return chiAmountForRewardInUsd; } function _getReservesData() public view returns (bool isExcessOfReserves, uint256 reserveDiff, uint256 reserveValue) { reserveValue = reserveHolder.getReserveValue(); uint256 uscTotalSupplyValue = _convertTokenAmountToUsdValue(USC.totalSupply(), USC_TARGET_PRICE); if (reserveValue > uscTotalSupplyValue) { isExcessOfReserves = true; reserveDiff = (reserveValue - uscTotalSupplyValue); } else { isExcessOfReserves = false; reserveDiff = (uscTotalSupplyValue - reserveValue); } } function _getAndValidateUscPrice(uint256 ethPrice) private view returns (uint256) { uint256 uscPrice = priceFeedAggregator.peek(address(USC)); uint256 uscSpotPrice = _calculateUscSpotPrice(ethPrice); uint256 priceDiff = _absDiff(uscSpotPrice, uscPrice); uint256 maxPriceDiff = Math.mulDiv(uscPrice, priceTolerance, MAX_PRICE_TOLERANCE); if (priceDiff > maxPriceDiff) { revert PriceSlippageTooBig(); } return uscSpotPrice; } function _validateArbitrage(uint256 maxChiSpotPrice) private view { if (!isPrivileged[msg.sender]) { uint256 ethPrice = priceFeedAggregator.peek(WETH); uint256 chiSpotPrice = _calculateChiSpotPrice(ethPrice); if (maxChiSpotPrice != 0 && chiSpotPrice > maxChiSpotPrice) { revert ChiSpotPriceTooBig(); } // If max chi spot price is not specified we need to check for twap difference if (maxChiSpotPrice == 0) { uint256 chiOraclePrice = priceFeedAggregator.peek(address(CHI)); if (!_almostEqualRel(chiSpotPrice, chiOraclePrice, chiPriceTolerance)) { revert ChiPriceNotPegged(chiSpotPrice, chiOraclePrice); } } } } // input ethPrice has 8 decimals // returns result with 8 decimals function _calculateUscSpotPrice(uint256 ethPrice) private view returns (uint256) { (uint256 reserveUSC, uint256 reserveWETH) = UniswapV2Library.getReserves( address(poolFactory), address(USC), address(WETH) ); uint256 uscFor1ETH = UniswapV2Library.quote(1 ether, reserveWETH, reserveUSC); return Math.mulDiv(ethPrice, 1 ether, uscFor1ETH); } // input ethPrice has 8 decimals // returns result with 8 decimals function _calculateChiSpotPrice(uint256 ethPrice) private view returns (uint256) { (uint256 reserveCHI, uint256 reserveWETH) = UniswapV2Library.getReserves( address(poolFactory), address(CHI), address(WETH) ); uint256 chiFor1ETH = UniswapV2Library.quote(1 ether, reserveWETH, reserveCHI); return Math.mulDiv(ethPrice, 1 ether, chiFor1ETH); } // what amount of In tokens to put in pool to make price: 1 tokenOut = (priceOut / priceIn) tokenIn // assuming reserves are on 18 decimals, prices are on 8 decimals function _calculateDelta( uint256 reserveIn, uint256 priceIn, uint256 reserveOut, uint256 priceOut ) public pure returns (uint256) { // F = (1 - pool_fee) = 0.997 on 18 decimals, in square root formula a = F // parameter `b` in square root formula, b = rIn * (1+f) , on 18 decimals uint256 b = Math.mulDiv(reserveIn, 1e18 + F, 1e18); uint256 b_sqr = Math.mulDiv(b, b, 1e18); // parameter `c` in square root formula, c = rIn^2 - (rIn * rOut * priceOut) / priceIn uint256 c_1 = Math.mulDiv(reserveIn, reserveIn, 1e18); uint256 c_2 = Math.mulDiv(Math.mulDiv(reserveIn, reserveOut, 1e18), priceOut, priceIn); uint256 c; uint256 root; if (c_1 > c_2) { c = c_1 - c_2; // d = 4ac uint256 d = Math.mulDiv(4 * F, c, 1e18); // root = sqrt(b^2 - 4ac) // multiplying by 10^9 to get back to 18 decimals root = Math.sqrt(b_sqr - d) * 1e9; } else { c = c_2 - c_1; // d = 4ac uint256 d = Math.mulDiv(4 * F, c, 1e18); // root = sqrt(b^2 - 4ac) -> in this case `c` is negative, so we add `d` to `b^2` // multiplying by 10^9 to get back to 18 decimals root = Math.sqrt(b_sqr + d) * 1e9; } // delta = (-b + root) / 2*f uint256 delta = Math.mulDiv(1e18, root - b, 2 * F); return delta; } // given ethPrice is on 8 decimals // how many USC to put in pool to make price: 1 ETH = ethPrice * USC function _calculateDeltaUSC(uint256 ethPrice) public view returns (uint256) { (uint256 reserveUSC, uint256 reserveWETH) = UniswapV2Library.getReserves( address(poolFactory), address(USC), address(WETH) ); return _calculateDelta(reserveUSC, USC_TARGET_PRICE, reserveWETH, ethPrice); } // how many ETH to put in pool to make price: 1 ETH = ethPrice * USC function _calculateDeltaETH(uint256 ethPrice) public view returns (uint256) { (uint256 reserveUSC, uint256 reserveWETH) = UniswapV2Library.getReserves( address(poolFactory), address(USC), address(WETH) ); return _calculateDelta(reserveWETH, ethPrice, reserveUSC, USC_TARGET_PRICE); } function _makePath(address t1, address t2) internal pure returns (address[] memory path) { path = new address[](2); path[0] = t1; path[1] = t2; } function _makePath(address t1, address t2, address t3) internal pure returns (address[] memory path) { path = new address[](3); path[0] = t1; path[1] = t2; path[2] = t3; } function _swap(address tokenIn, address tokenOut, uint256 amount) private returns (uint256) { address[] memory path; if (tokenIn != WETH && tokenOut != WETH) { path = _makePath(tokenIn, WETH, tokenOut); } else { path = _makePath(tokenIn, tokenOut); } IERC20(tokenIn).approve(address(swapRouter), amount); uint256[] memory amounts = swapRouter.swapExactTokensForTokens(amount, 0, path, address(this), block.timestamp); uint256 amountReceived = amounts[path.length - 1]; return amountReceived; } function _getAmountInForAmountOut(address tIn, address tOut, uint256 amountOut) internal view returns (uint256) { (uint256 rIn, uint256 rOut) = UniswapV2Library.getReserves(address(poolFactory), tIn, tOut); return UniswapV2Library.getAmountIn(amountOut, rIn, rOut); } function _convertUsdValueToTokenAmount(uint256 usdValue, uint256 price) internal pure returns (uint256) { return Math.mulDiv(usdValue, 1e18, price); } function _convertTokenAmountToUsdValue(uint256 amount, uint256 price) internal pure returns (uint256) { return Math.mulDiv(amount, price, 1e18); } function _absDiff(uint256 a, uint256 b) internal pure returns (uint256) { return (a > b) ? a - b : b - a; } function _almostEqualAbs(uint256 price1, uint256 price2, uint256 delta) internal pure returns (bool) { return _absDiff(price1, price2) <= delta; } function _almostEqualRel(uint256 price1, uint256 price2, uint256 delta) internal pure returns (bool) { (uint256 highPrice, uint256 lowPrice) = price1 > price2 ? (price1, price2) : (price2, price1); uint256 priceDiff = highPrice - lowPrice; uint256 maxPriceDiff = Math.mulDiv(highPrice, delta, MAX_PRICE_TOLERANCE); return priceDiff <= maxPriceDiff; } receive() external payable {} }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/utils/math/Math.sol"; import "@openzeppelin/contracts/utils/math/Math.sol"; import "../interfaces/IReserveHolder.sol"; import "../interfaces/IPriceFeedAggregator.sol"; import "../interfaces/ISTETH.sol"; import "../interfaces/IWETH.sol"; import "../interfaces/ICurvePool.sol"; import "../library/ExternalContractAddresses.sol"; /// @title Contract for holding stETH reserves /// @notice This contract holds stETH reserves and rebalances them /// @notice Part of reserves are is WETH so arbitrage can take them and perform aribtrage without swapping stETH for WETH /// @dev This contract is upgradeable contract ReserveHolder is IReserveHolder, OwnableUpgradeable { using SafeERC20 for ISTETH; using SafeERC20 for IWETH; uint256 public constant BASE_PRICE = 1e8; uint256 public constant MAX_PERCENTAGE = 100_00; IWETH public constant WETH = IWETH(ExternalContractAddresses.WETH); ISTETH public constant stETH = ISTETH(ExternalContractAddresses.stETH); ICurvePool public constant curvePool = ICurvePool(ExternalContractAddresses.CURVE_ETH_STETH_POOL); IPriceFeedAggregator public priceFeedAggregator; address public claimer; uint256 public totalClaimed; uint256 public swapEthTolerance; uint256 public ethThreshold; uint256 public totalStEthDeposited; uint256 public curveStEthSafeGuardPercentage; mapping(address account => bool status) public isArbitrager; modifier onlyArbitrager() { if (isArbitrager[msg.sender] != true) { revert NotArbitrager(msg.sender); } _; } modifier onlyClaimer() { if (msg.sender != claimer) { revert NotClaimer(msg.sender); } _; } receive() external payable { emit Receive(msg.sender, msg.value); } function initialize( IPriceFeedAggregator _priceFeedAggregator, address _claimer, uint256 _ethThreshold, uint256 _curveStEthSafeGuardPercentage ) external initializer { if (_ethThreshold > MAX_PERCENTAGE) { revert ThresholdTooHigh(_ethThreshold); } __Ownable_init(); claimer = _claimer; priceFeedAggregator = _priceFeedAggregator; ethThreshold = _ethThreshold; curveStEthSafeGuardPercentage = _curveStEthSafeGuardPercentage; swapEthTolerance = 0.1 ether; } /// @inheritdoc IReserveHolder function setArbitrager(address arbitrager, bool status) external onlyOwner { isArbitrager[arbitrager] = status; emit SetArbitrager(arbitrager, status); } /// @inheritdoc IReserveHolder function setClaimer(address _claimer) external onlyOwner { claimer = _claimer; emit SetClaimer(_claimer); } /// @inheritdoc IReserveHolder function setEthThreshold(uint256 _ethThreshold) external onlyOwner { if (_ethThreshold > MAX_PERCENTAGE) { revert ThresholdTooHigh(_ethThreshold); } ethThreshold = _ethThreshold; emit SetEthThreshold(_ethThreshold); } /// @inheritdoc IReserveHolder function setSwapEthTolerance(uint256 _swapEthTolerance) external onlyOwner { swapEthTolerance = _swapEthTolerance; emit SetSwapEthTolerance(_swapEthTolerance); } /// @inheritdoc IReserveHolder function setCurveStEthSafeGuardPercentage(uint256 _curveStEthSafeGuardPercentage) external onlyOwner { if (_curveStEthSafeGuardPercentage > MAX_PERCENTAGE) { revert SafeGuardTooHigh(_curveStEthSafeGuardPercentage); } curveStEthSafeGuardPercentage = _curveStEthSafeGuardPercentage; emit SetCurveStEthSafeGuardPercentage(_curveStEthSafeGuardPercentage); } /// @inheritdoc IReserveHolder function getReserveValue() external view returns (uint256) { uint256 stEthPrice = priceFeedAggregator.peek(address(stETH)); uint256 ethPrice = priceFeedAggregator.peek(address(WETH)); uint256 ethBalance = address(this).balance + WETH.balanceOf(address(this)); return Math.mulDiv(totalStEthDeposited, stEthPrice, 1e18) + Math.mulDiv(ethBalance, ethPrice, 1e18); } /// @inheritdoc IReserveHolder function getCurrentRewards() external view returns (uint256) { return stETH.balanceOf(address(this)) - totalStEthDeposited; } /// @inheritdoc IReserveHolder function getCumulativeRewards() external view returns (uint256) { return stETH.balanceOf(address(this)) - totalStEthDeposited + totalClaimed; } /// @inheritdoc IReserveHolder function deposit(uint256 amount) external { uint256 balanceBefore = stETH.balanceOf(address(this)); stETH.safeTransferFrom(msg.sender, address(this), amount); totalStEthDeposited += stETH.balanceOf(address(this)) - balanceBefore; emit Deposit(msg.sender, amount); } /// @inheritdoc IReserveHolder function rebalance() external { uint256 ethPrice = _peek(address(WETH)); uint256 stEthPrice = _peek(address(stETH)); uint256 ethValue = Math.mulDiv(WETH.balanceOf(address(this)), ethPrice, BASE_PRICE); uint256 stEthValue = Math.mulDiv(stETH.balanceOf(address(this)), stEthPrice, BASE_PRICE); uint256 ethThresholdValue = Math.mulDiv((ethValue + stEthValue), ethThreshold, MAX_PERCENTAGE); if (ethThresholdValue > ethValue) { uint256 stEthAmountToSwap = Math.mulDiv((ethThresholdValue - ethValue), BASE_PRICE, stEthPrice); uint256 stEthBalanceBefore = stETH.balanceOf(address(this)); _swap(stEthAmountToSwap); uint256 stEthBalanceAfter = stETH.balanceOf(address(this)); totalStEthDeposited -= stEthBalanceBefore - stEthBalanceAfter; emit Rebalance(0, stEthAmountToSwap); } else if (ethThresholdValue < ethValue) { uint256 stEthBalanceBefore = stETH.balanceOf(address(this)); uint256 ethAmountToSwap = Math.mulDiv((ethValue - ethThresholdValue), BASE_PRICE, ethPrice); WETH.withdraw(ethAmountToSwap); stETH.submit{value: ethAmountToSwap}(address(this)); uint256 stEthBalanceAfter = stETH.balanceOf(address(this)); totalStEthDeposited += stEthBalanceAfter - stEthBalanceBefore; emit Rebalance(ethAmountToSwap, 0); } } /// @inheritdoc IReserveHolder function redeem(uint256 amount) external onlyArbitrager returns (uint256) { uint256 ethBalance = WETH.balanceOf(address(this)); if (amount > ethBalance) { uint256 stEthBalanceBefore = stETH.balanceOf(address(this)); uint256 stEthAmountToSwap = amount - ethBalance; uint256 safeStEthAmountToSwap = stEthAmountToSwap + Math.mulDiv(stEthAmountToSwap, curveStEthSafeGuardPercentage, MAX_PERCENTAGE); _swap(safeStEthAmountToSwap); uint256 stEthBalanceAfter = stETH.balanceOf(address(this)); totalStEthDeposited -= stEthBalanceBefore - stEthBalanceAfter; emit RedeemSwap(amount - ethBalance, safeStEthAmountToSwap); } WETH.safeTransfer(msg.sender, amount); emit Redeem(msg.sender, amount); return amount; } /// @inheritdoc IReserveHolder function claimRewards(address account, uint256 amount) external onlyClaimer { totalClaimed += amount; stETH.safeTransfer(account, amount); emit ClaimRewards(account, amount); } /// @inheritdoc IReserveHolder function wrapETH() external { WETH.deposit{value: address(this).balance}(); } function _swap(uint256 amountIn) private { stETH.approve(address(curvePool), amountIn); uint256 ethReceived = curvePool.exchange(1, 0, amountIn, 0); WETH.deposit{value: ethReceived}(); } function _peek(address asset) private view returns (uint256) { uint256 price = priceFeedAggregator.peek(asset); return price; } function _safeTransferETH(address to, uint256 value) private { (bool success, ) = to.call{value: value}(new bytes(0)); if (!success) { revert EtherSendFailed(to, value); } } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/utils/math/Math.sol"; import "@openzeppelin/contracts/utils/math/SafeCast.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol"; import "../interfaces/ILPRewards.sol"; import "../interfaces/IPriceFeedAggregator.sol"; import "./PoolHelper.sol"; /// @title Contract for handling LP rewards /// @notice Each LP token has its own LPRewards contract contract LPRewards is ILPRewards, Ownable { using SafeERC20 for IERC20; using SafeCast for uint256; uint8 public immutable decimals; IUniswapV2Pair public immutable lpToken; IPriceFeedAggregator public immutable priceFeedAggregator; address public ochi; uint64 public currentEpoch; int256 public totalAmountLocked; uint256 public epochMinLPBalance; uint256 public currentLPValue; int256 public cumulativeProfit; mapping(uint256 tokenId => LockingTokenData) public lockingTokenData; mapping(uint256 epochId => EpochData) public epochData; modifier onlyOCHI() { if (msg.sender != ochi) { revert NotOCHI(); } _; } constructor(IUniswapV2Pair _lpToken, IPriceFeedAggregator _priceFeedAggregator) { lpToken = _lpToken; decimals = lpToken.decimals(); priceFeedAggregator = _priceFeedAggregator; currentEpoch = 1; totalAmountLocked = 0; } function setOCHI(address _ochi) external onlyOwner { ochi = _ochi; emit SetOCHI(_ochi); } /// @notice Locks LP tokens for given period, user gets rewards until the end of the period /// @param lockingTokenId Unique OCHI id /// @param amount Amount of LP tokens to lock /// @param epochDuration Locking duration in epochs /// @custom:usage This function should be called from OCHI contract in purpose of locking LP tokens function lockLP(uint256 lockingTokenId, uint256 amount, uint64 epochDuration) external onlyOCHI { if (amount == 0) { return; } LockingTokenData storage position = lockingTokenData[lockingTokenId]; if (position.amountLocked != 0) { revert LockingTokenIdAlreadyUsed(lockingTokenId); } uint64 nextEpoch = currentEpoch + 1; epochData[nextEpoch].totalDeltaAmountLocked += amount.toInt256(); epochData[nextEpoch + epochDuration].totalDeltaAmountLocked -= amount.toInt256(); position.amountLocked = amount; position.endingEpoch = nextEpoch + epochDuration; position.lastClaimedEpoch = currentEpoch; emit LockLP(lockingTokenId, amount, currentEpoch, position.endingEpoch); } /// @notice Claims rewards for given token id /// @param lockingTokenId Unique OCHI id /// @param account Account to send rewards to /// @custom:usage This function should be called from OCHI contract in purpose of claiming rewards function claimRewards(uint256 lockingTokenId, address account) external onlyOCHI { int256 rewardUSD = calculateUnclaimedReward(lockingTokenId); if (rewardUSD < 0) { return; } uint256 totalPoolValue = PoolHelper.getTotalPoolUSDValue(lpToken, priceFeedAggregator); uint256 lpTokensToBurn = Math.mulDiv(uint256(rewardUSD), lpToken.totalSupply(), totalPoolValue); if (lpTokensToBurn == 0) { return; } IERC20(address(lpToken)).safeTransfer(address(lpToken), lpTokensToBurn); lpToken.burn(account); uint256 newBalance = lpToken.balanceOf(address(this)); if (newBalance < epochMinLPBalance) epochMinLPBalance = newBalance; lockingTokenData[lockingTokenId].lastClaimedEpoch = currentEpoch - 1; emit ClaimRewards(lockingTokenId, account, lpTokensToBurn, rewardUSD); } /// @notice End current epoch and start new one /// @custom:usage This function should be called from OCHI contract when epoch is updated function updateEpoch() external onlyOCHI { EpochData storage epoch = epochData[currentEpoch]; totalAmountLocked += epoch.totalDeltaAmountLocked; uint256 prevLPValue = currentLPValue; currentLPValue = PoolHelper.getUSDValueForLP(10 ** decimals, lpToken, priceFeedAggregator); int256 totalProfit = (currentLPValue.toInt256() - prevLPValue.toInt256()) * epochMinLPBalance.toInt256(); int256 profitPerLockedToken; if (totalAmountLocked != 0) { profitPerLockedToken = totalProfit / totalAmountLocked; } cumulativeProfit += profitPerLockedToken; epoch.cumulativeProfit = cumulativeProfit; epochMinLPBalance = lpToken.balanceOf(address(this)); currentEpoch++; emit UpdateEpoch(currentEpoch - 1, currentLPValue, totalAmountLocked, profitPerLockedToken); } /// @notice Takes LP tokens from contract and sends them to receiver /// @param receiver Account to send LP tokens to /// @custom:usage This function should be called only in case of moving liquidity to another pool function recoverLPTokens(address receiver) external onlyOCHI { uint256 amount = lpToken.balanceOf(address(this)); IERC20(address(lpToken)).safeTransfer(receiver, amount); emit RecoverLPTokens(receiver, amount); } /// @notice Calculates unclaimed rewards for given token id /// @param lockingTokenId Unique OCHI id function calculateUnclaimedReward(uint256 lockingTokenId) public view returns (int256) { LockingTokenData storage position = lockingTokenData[lockingTokenId]; if (position.endingEpoch == 0) { return 0; } uint64 fromEpoch = position.lastClaimedEpoch; uint64 toEpoch = currentEpoch - 1; if (position.endingEpoch - 1 < toEpoch) toEpoch = position.endingEpoch - 1; if (toEpoch <= fromEpoch) return 0; int256 profitDelta = epochData[toEpoch].cumulativeProfit - epochData[fromEpoch].cumulativeProfit; int256 totalUSDreward = (position.amountLocked.toInt256() * profitDelta) / (10 ** decimals).toInt256(); return totalUSDreward; } /// @notice Calculates the profit in last epoch in usd value function getLastEpochProfit() external view returns (int256) { if (currentEpoch < 2) return 0; return epochData[currentEpoch - 1].cumulativeProfit - epochData[currentEpoch - 2].cumulativeProfit; } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721EnumerableUpgradeable.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol"; import "@openzeppelin/contracts/utils/math/Math.sol"; import "../interfaces/IOCHI.sol"; import "../interfaces/IPriceFeedAggregator.sol"; import "../interfaces/IMintableBurnable.sol"; import "../interfaces/ILPRewards.sol"; import "../library/ExternalContractAddresses.sol"; import "./PoolHelper.sol"; /// @title Contract for creating and executing options /// @notice Each LP token has its own LPRewards contract /// @dev This contract is upgradeable /// @dev This contract handles creation and execution of options but does not hold LP tokens contract OCHI is IOCHI, ERC721EnumerableUpgradeable, OwnableUpgradeable { using SafeERC20 for IERC20; using SafeERC20 for IMintableBurnable; using SafeERC20 for IUniswapV2Pair; uint256 public constant MAX_LOCK_PERIOD_EPOCHS = 52; uint256 public constant EPOCH_DURATION = 1 weeks; uint256 public constant BASE_PRICE = 1e8; uint256 public constant MULTIPLIER = 1e18; uint256 public constant TARGET_RATIO = 80_00; uint256 public constant DENOMINATOR = 100_00; address public constant WETH = ExternalContractAddresses.WETH; IERC20 public usc; IMintableBurnable public chi; IUniswapV2Pair public uscEthPair; IUniswapV2Pair public chiEthPair; IPriceFeedAggregator public priceFeedAggregator; uint64 public currentEpoch; uint256 public firstEpochTimestamp; uint256 public mintedOCHI; uint256 public totalOCHIlocked; mapping(IUniswapV2Pair token => ILPRewards) public lpRewards; mapping(uint256 id => ChiOption) public options; // Upgrade bool public pauseMint; bool public pauseBurn; bool public pauseClaimRewards; modifier notPausedMint() { if (pauseMint) { revert MintPaused(); } _; } modifier notPausedBurn() { if (pauseBurn) { revert BurnPaused(); } _; } modifier notPausedClaimRewards() { if (pauseClaimRewards) { revert ClaimRewardsPaused(); } _; } function initialize( IERC20 _usc, IMintableBurnable _chi, IPriceFeedAggregator _priceFeedAggregator, IUniswapV2Pair _uscEthPair, IUniswapV2Pair _chiEthPair, ILPRewards _uscEthPairRewards, ILPRewards _chiEthPairRewards, uint256 _firstEpochTimestamp ) external initializer { __ERC721_init("Option CHI", "oCHI"); __Ownable_init(); usc = _usc; chi = _chi; priceFeedAggregator = _priceFeedAggregator; uscEthPair = _uscEthPair; chiEthPair = _chiEthPair; lpRewards[_uscEthPair] = _uscEthPairRewards; lpRewards[_chiEthPair] = _chiEthPairRewards; currentEpoch = 1; firstEpochTimestamp = _firstEpochTimestamp; } /// @inheritdoc IOCHI function setPauseMint(bool _pauseMint) external onlyOwner { pauseMint = _pauseMint; } /// @inheritdoc IOCHI function setPauseBurn(bool _pauseBurn) external onlyOwner { pauseBurn = _pauseBurn; } /// @inheritdoc IOCHI function setPauseClaimRewards(bool _pauseClaimRewards) external onlyOwner { pauseClaimRewards = _pauseClaimRewards; } /// @inheritdoc IOCHI function mint( uint256 chiAmount, uint256 uscEthPairAmount, uint256 chiEthPairAmount, uint64 lockPeriodInEpochs ) external notPausedMint { if (lockPeriodInEpochs > MAX_LOCK_PERIOD_EPOCHS || lockPeriodInEpochs == 0) { revert InvalidLockPeriod(lockPeriodInEpochs); } (uint256 strikePrice, uint256 oChiAmount) = calculateOptionData( chiAmount, uscEthPairAmount, chiEthPairAmount, lockPeriodInEpochs ); uint64 nextEpoch = currentEpoch + 1; uint256 tokenId = ++mintedOCHI; options[tokenId] = ChiOption({ amount: oChiAmount, strikePrice: strikePrice, uscEthPairAmount: uscEthPairAmount, chiEthPairAmount: chiEthPairAmount, lockedUntil: nextEpoch + lockPeriodInEpochs, validUntil: nextEpoch + 2 * lockPeriodInEpochs }); totalOCHIlocked += oChiAmount; ILPRewards uscEthLPRewards = lpRewards[uscEthPair]; ILPRewards chiEthLPRewards = lpRewards[chiEthPair]; if (chiAmount > 0) { chi.burnFrom(msg.sender, chiAmount); } IERC20(address(uscEthPair)).safeTransferFrom(msg.sender, address(uscEthLPRewards), uscEthPairAmount); IERC20(address(chiEthPair)).safeTransferFrom(msg.sender, address(chiEthLPRewards), chiEthPairAmount); uscEthLPRewards.lockLP(tokenId, uscEthPairAmount, uint64(lockPeriodInEpochs)); chiEthLPRewards.lockLP(tokenId, chiEthPairAmount, uint64(lockPeriodInEpochs)); _safeMint(msg.sender, tokenId); emit Mint(tokenId, chiAmount, uscEthPairAmount, chiEthPairAmount, lockPeriodInEpochs, strikePrice, oChiAmount); } /// @inheritdoc IOCHI function burn(uint256 tokenId) external notPausedBurn { if (!_isApprovedOrOwner(msg.sender, tokenId)) { revert NotAllowed(tokenId); } ChiOption storage option = options[tokenId]; if (currentEpoch < option.lockedUntil) { revert OptionLocked(tokenId); } if (currentEpoch > option.validUntil) { revert OptionExpired(tokenId); } lpRewards[uscEthPair].claimRewards(tokenId, msg.sender); lpRewards[chiEthPair].claimRewards(tokenId, msg.sender); uint256 chiAmount = option.amount; uint256 chiBalance = chi.balanceOf(address(this)); uint256 amountToTransfer = Math.min(chiAmount, chiBalance); uint256 amountToMint = chiAmount - amountToTransfer; if (amountToMint > 0) { chi.mint(msg.sender, amountToMint); } if (amountToTransfer > 0) { chi.transfer(msg.sender, amountToTransfer); } _burn(tokenId); totalOCHIlocked -= options[tokenId].amount; emit Burn(msg.sender, tokenId, chiAmount); } /// @inheritdoc IOCHI function updateEpoch() public { if (block.timestamp < firstEpochTimestamp + (currentEpoch - 1) * EPOCH_DURATION) { revert EpochNotFinished(); } lpRewards[uscEthPair].updateEpoch(); lpRewards[chiEthPair].updateEpoch(); currentEpoch++; emit UpdateEpoch(currentEpoch - 1, block.timestamp); } /// @inheritdoc IOCHI function claimRewards(uint256 tokenId) public notPausedClaimRewards { if (!_isApprovedOrOwner(msg.sender, tokenId)) { revert NotAllowed(tokenId); } lpRewards[uscEthPair].claimRewards(tokenId, msg.sender); lpRewards[chiEthPair].claimRewards(tokenId, msg.sender); emit ClaimRewardsOCHI(tokenId); } /// @notice Claims rewards for all users tokens function claimAllRewards() public { uint256 balance = balanceOf(msg.sender); for (uint256 i = 0; i < balance; i++) { claimRewards(tokenOfOwnerByIndex(msg.sender, i)); } } /// @inheritdoc IOCHI function recoverLPTokens() external onlyOwner { lpRewards[uscEthPair].recoverLPTokens(msg.sender); lpRewards[chiEthPair].recoverLPTokens(msg.sender); emit RecoverLPTokens(); } /// @inheritdoc IOCHI function getUnclaimedRewardsValue(uint256 tokenId) external view returns (int256) { return lpRewards[uscEthPair].calculateUnclaimedReward(tokenId) + lpRewards[chiEthPair].calculateUnclaimedReward(tokenId); } /// @notice Calulates strike price and ochi amount for supplied asets /// @param chiAmount amount of chi token to supply /// @param uscEthPairAmount amount of USC/ETH LP token to supply /// @param chiEthPairAmount amount of CHI/ETH LP token to supply /// @param lockPeriodInEpochs locking duration /// @return strikePrice strike price given for supplied assets /// @return oChiAmount ochi amount given for supplied assets function calculateOptionData( uint256 chiAmount, uint256 uscEthPairAmount, uint256 chiEthPairAmount, uint256 lockPeriodInEpochs ) public view returns (uint256 strikePrice, uint256 oChiAmount) { uint256 timeMultiplier = Math.mulDiv(lockPeriodInEpochs, MULTIPLIER, 4 * MAX_LOCK_PERIOD_EPOCHS); uint256 chiMultiplier = Math.mulDiv(chiAmount, MULTIPLIER, chi.totalSupply()); (uint256 poolMultiplier, uint256 positionsValue) = getAndValidatePositionsData(uscEthPairAmount, chiEthPairAmount); uint256 discount = timeMultiplier + Math.min(poolMultiplier + chiMultiplier, MULTIPLIER / 4); uint256 chiPrice = _peek(address(chi)); strikePrice = Math.mulDiv(chiPrice, MULTIPLIER - discount, MULTIPLIER); uint256 chiValue = Math.mulDiv(chiAmount, chiPrice, MULTIPLIER); oChiAmount = Math.mulDiv(positionsValue + chiValue, MULTIPLIER, strikePrice); } /// @notice Calculates multiplier for supplied LP assets /// @param uscEthPairAmount amount of USC/ETH LP token to supply /// @param chiEthPairAmount amount of CHI/ETH LP token to supply /// @return multiplier multiplier used for caluclating discount /// @param value parameter used for calculating ochi amount function getAndValidatePositionsData( uint256 uscEthPairAmount, uint256 chiEthPairAmount ) public view returns (uint256 multiplier, uint256 value) { uint256 uscEthPairTotalSupply = uscEthPair.totalSupply(); uint256 chiEthPairTotalSupply = chiEthPair.totalSupply(); if ( uscEthPair.balanceOf(address(this)) + uscEthPairAmount > Math.mulDiv(uscEthPairTotalSupply, TARGET_RATIO, DENOMINATOR) || chiEthPair.balanceOf(address(this)) + chiEthPairAmount > Math.mulDiv(chiEthPairTotalSupply, TARGET_RATIO, DENOMINATOR) ) { revert PolTargetRatioExceeded(); } multiplier = Math.mulDiv(uscEthPairAmount, MULTIPLIER, uscEthPairTotalSupply) + Math.mulDiv(chiEthPairAmount, MULTIPLIER, chiEthPairTotalSupply); value = (PoolHelper.getUSDValueForLP(uscEthPairAmount, uscEthPair, priceFeedAggregator) + PoolHelper.getUSDValueForLP(chiEthPairAmount, chiEthPair, priceFeedAggregator)); } /// @notice Calculates the total LP reward for the last epoch /// @return totalReward total reward in usd value function getLastEpochTotalReward() external view returns (int256 totalReward) { if (currentEpoch < 2) return 0; return lpRewards[uscEthPair].getLastEpochProfit() + lpRewards[chiEthPair].getLastEpochProfit(); } function _peek(address asset) internal view returns (uint256 price) { price = priceFeedAggregator.peek(asset); } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol"; import "@openzeppelin/contracts/utils/math/Math.sol"; import "../interfaces/IPriceFeedAggregator.sol"; library PoolHelper { function getTotalPoolUSDValue( IUniswapV2Pair pair, IPriceFeedAggregator priceFeedAggregator ) internal view returns (uint256) { (uint112 token0amount, uint112 token1amount, ) = pair.getReserves(); uint256 price0 = priceFeedAggregator.peek(pair.token0()); uint256 price1 = priceFeedAggregator.peek(pair.token1()); // assuming both tokens have 18 decimals! uint256 totalValue = Math.mulDiv(token0amount, price0, 1e18) + Math.mulDiv(token1amount, price1, 1e18); return totalValue; } function getUSDValueForLP( uint256 lpAmount, IUniswapV2Pair pair, IPriceFeedAggregator priceFeedAggregator ) internal view returns (uint256) { return Math.mulDiv(getTotalPoolUSDValue(pair, priceFeedAggregator), lpAmount, pair.totalSupply()); } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; interface IAdapter { enum Pool { UNISWAP_V2, UNISWAP_V3, CURVE, LIDO, ETHER_FI } event RescueReserves(); event SetPoolType(Pool indexed poolType); event Deposit(uint256 amount); event ClaimRewards(address receiver, uint256 amount); event Withdraw(uint256 amount, address recipient); error NotReserveHolder(); /// @notice Gets reserve value in USD /// @return reserveValue Reserve value in USD function getReserveValue() external view returns (uint256 reserveValue); /// @notice Gets total deposited amount /// @return totalDeposited Total deposited amount function totalDeposited() external view returns (uint256 totalDeposited); /// @notice Rescue reserves from contract /// @dev Only owner can call this function function rescueReserves() external; /// @notice Sets pool type /// @param _poolType Pool type /// @dev Only owner can call this function function setPoolType(Pool _poolType) external; /// @notice Deposit asset to reserve /// @param amount Amount of asset to deposit function deposit(uint256 amount) external; /// @notice Withdraw asset from reserve /// @param amount Amount of asset to withdraw /// @param recipient Receiver of asset function withdraw(uint256 amount, address recipient) external; /// @notice Claim rewards from reserve /// @param receiver Receiver of rewards /// @return amount Amount of rewards claimed function claimRewards(address receiver) external returns (uint256 amount); /// @notice Sells LST for ETH when needed for arbitrage or rebalance /// @param amountIn Amount of LST to sell /// @param minAmountOut Minimum amount of ETH to receive /// @param receiver Receiver of ETH function swapAmountToEth(uint256 amountIn, uint256 minAmountOut, address receiver) external returns (uint256); /// @notice Sells ETH for LST /// @param amountIn Amount of ETH to sell function swapAmountFromEth(uint256 amountIn) external returns (uint256); }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; interface IArbitrage { event SetPriceTolerance(uint16 priceTolerance); event SetMaxMintPriceDiff(uint256 maxMintPriceDiff); event Mint(address indexed account, address token, uint256 amount, uint256 uscAmount); event ExecuteArbitrage( address indexed account, uint256 indexed arbNum, uint256 deltaUsd, uint256 reserveDiff, uint256 ethPrice, uint256 rewardValue ); error DeltaBiggerThanAmountReceivedETH(uint256 deltaETH, uint256 receivedETH); error ToleranceTooBig(uint16 _tolerance); error PriceSlippageTooBig(); /// @notice Sets spot price tolerance from TWAP price /// @dev 100% = 10000 /// @param _priceTolerance Price tolerance in percents /// @custom:usage This function should be called from owner in purpose of setting price tolerance function setPriceTolerance(uint16 _priceTolerance) external; /// @notice Sets max mint price diff /// @param _maxMintPriceDiff Max mint price diff /// @custom:usage This function should be called from owner in purpose of setting max mint price diff function setMaxMintPriceDiff(uint256 _maxMintPriceDiff) external; /// @notice Mint USC tokens for ETH /// @dev If USC price is different from target price for less then max mint price diff, then minting is allowed without performing arbitrage /// @return uscAmount Amount of USC tokens minted function mint() external payable returns (uint256 uscAmount); /// @notice Mint USC tokens for WETH /// @dev If USC price is different from target price for less then max mint price diff, then minting is allowed without performing arbitrage /// @param wethAmount Amount of WETH to mint with /// @return uscAmount Amount of USC tokens minted function mintWithWETH(uint256 wethAmount) external returns (uint256 uscAmount); /// @notice Mint USC tokens for stETH /// @dev If USC price is different from target price for less then max mint price diff, then minting is allowed without performing arbitrage /// @param stETHAmount Amount of stETH to mint with /// @return uscAmount Amount of USC tokens minted function mintWithStETH(uint256 stETHAmount) external returns (uint256 uscAmount); /// @notice Executes arbitrage, profit sent to caller /// @notice Returns reward value in USD /// @return rewardValue Reward value in USD /// @custom:usage This function should be called from external keeper in purpose of pegging USC price and getting reward /// @custom:usage This function has no restrictions, anyone can be arbitrager function executeArbitrage() external returns (uint256 rewardValue); /// @notice Gets information for perfoming arbitrage such as price diff, reserve diff, discount /// @return isPriceAboveTarget True if USC price is above target price /// @return isExcessOfReserves True if there is excess of reserves /// @return reserveDiff Reserve diff, excess or deficit of reserves /// @return discount Discount in percents, only if price is equal to target price function getArbitrageData() external view returns (bool isPriceAboveTarget, bool isExcessOfReserves, uint256 reserveDiff, uint256 discount); }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; interface IArbitrageERC20 is IERC20 { function mint(address to, uint256 amount) external; function burn(uint256 amount) external; }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {IArbitrage} from "./IArbitrage.sol"; interface IArbitrageV2 is IArbitrage { event UpdateArbitrager(address indexed account, bool status); error NotArbitrager(address account); error PriceIsNotPegged(); /// @notice Update arbitrager status /// @dev This function can be called only by owner of contract /// @param account Arbitrager account /// @param status Arbitrager status function updateArbitrager(address account, bool status) external; /// @notice Claim rewards from arbitrages /// @dev This function can be called only by owner of contract /// @param tokens Tokens to claim rewards for function claimRewards(IERC20[] memory tokens) external; }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; interface IArbitrageV3 { error DeltaBiggerThanAmountReceivedETH(uint256 deltaETH, uint256 receivedETH); error ToleranceTooBig(uint16 _tolerance); error PriceSlippageTooBig(); error NotArbitrager(address account); error PriceIsNotPegged(); error ReserveDiffTooBig(); error ChiPriceNotPegged(uint256 spotPrice, uint256 twapPrice); error FeeTooBig(uint256 fee); error ChiSpotPriceTooBig(); error ContractIsPaused(); event SetPriceTolerance(uint16 priceTolerance); event Mint(address indexed from, address indexed to, address token, uint256 amount, uint256 uscAmount); event ExecuteArbitrage( address indexed account, uint256 indexed arbNum, uint256 deltaUsd, uint256 reserveDiff, uint256 ethPrice, uint256 rewardValue ); event UpdateArbitrager(address indexed account, bool status); event SetMaxMintBurnPriceDiff(uint256 maxMintBurnPriceDiff); event SetChiPriceTolerance(uint16 chiPriceTolerance); event SetMaxMintBurnReserveTolerance(uint16 maxBurnReserveTolerance); event SetMintBurnFee(uint256 mintFee); event UpdatePrivileged(address indexed privileged, bool isPrivileged); event Burn(address from, address to, uint256 amount, uint256 ethAmount); event RewardUSC(uint256 amount); /// @notice Sets absolute peg price tolerance /// @param _priceTolerance Absolute value of price tolerance /// @custom:usage This function should be called from owner in purpose of setting price tolerance function setPegPriceToleranceAbs(uint256 _priceTolerance) external; /// @notice Sets spot price tolerance from TWAP price /// @dev 100% = 10000 /// @param _priceTolerance Price tolerance in percents /// @custom:usage This function should be called from owner in purpose of setting price tolerance function setPriceTolerance(uint16 _priceTolerance) external; /// @notice Mint USC tokens for ETH /// @dev If USC price is different from target price for less then max mint price diff, then minting is allowed without performing arbitrage /// @param receiver Receiver of USC tokens /// @return uscAmount Amount of USC tokens minted function mint(address receiver) external payable returns (uint256 uscAmount); /// @notice Mint USC tokens for WETH /// @dev If USC price is different from target price for less then max mint price diff, then minting is allowed without performing arbitrage /// @param wethAmount Amount of WETH to mint with /// @param receiver Receiver of USC tokens /// @return uscAmount Amount of USC tokens minted function mintWithWETH(uint256 wethAmount, address receiver) external returns (uint256 uscAmount); /// @notice Mint USC tokens for stETH /// @dev If USC price is different from target price for less then max mint price diff, then minting is allowed without performing arbitrage /// @param stETHAmount Amount of stETH to mint with /// @param receiver Receiver of USC tokens /// @return uscAmount Amount of USC tokens minted function mintWithStETH(uint256 stETHAmount, address receiver) external returns (uint256 uscAmount); /// @notice Executes arbitrage, profit sent to caller /// @notice Returns reward value in USD /// @param maxChiSpotPrice maximum spot price of CHI, if 0 TWAP check will be done /// @return rewardValue Reward value in USD /// @custom:usage This function should be called from external keeper in purpose of pegging USC price and getting reward /// @custom:usage This function has no restrictions, anyone can be arbitrager function executeArbitrage(uint256 maxChiSpotPrice) external returns (uint256 rewardValue); /// @notice Gets information for perfoming arbitrage such as price diff, reserve diff, discount /// @return isPriceAboveTarget True if USC price is above target price /// @return isExcessOfReserves True if there is excess of reserves /// @return reserveDiff Reserve diff, excess or deficit of reserves /// @return discount Discount in percents, only if price is equal to target price function getArbitrageData() external view returns (bool isPriceAboveTarget, bool isExcessOfReserves, uint256 reserveDiff, uint256 discount); /// @notice Update arbitrager status /// @dev This function can be called only by owner of contract /// @param account Arbitrager account /// @param status Arbitrager status function updateArbitrager(address account, bool status) external; /// @notice Claim rewards from arbitrages /// @dev This function can be called only by owner of contract /// @param tokens Tokens to claim rewards for /// @param amounts Amounts of tokens to claim rewards for function claimRewards(IERC20[] memory tokens, uint256[] memory amounts) external; /// @notice Rewards USC /// @dev This function can be called only by owner of contract /// @param amount Amount of USC to reward function rewardUSC(uint256 amount) external; /// @notice Sets maximum mint and burn price difference /// @dev This function can be called only by owner of contract, value is absolute /// @param _maxMintBurnPriceDiff Maximum mint and burn price difference function setMaxMintBurnPriceDiff(uint256 _maxMintBurnPriceDiff) external; /// @notice Sets CHI price tolerance percentage when checking TWAP /// @dev This function can be called only by owner of contract, value is relative /// @param _chiPriceTolerance CHI price tolerance percentage function setChiPriceTolerance(uint16 _chiPriceTolerance) external; /// @notice Sets maximum mint and burn price difference /// @dev This function can be called only by owner of contract, value is relative /// @param _maxMintBurnReserveTolerance Maximum mint and burn reserve tolerance function setMaxMintBurnReserveTolerance(uint16 _maxMintBurnReserveTolerance) external; /// @notice Sets mint and burn fee /// @dev This function can be called only by owner of contract /// @param _mintBurnFee Mint and burn fee function setMintBurnFee(uint16 _mintBurnFee) external; /// @notice Update privilege status, only privileged accounts can call arbitrage and pass CHI TWAP check /// @dev This function can be called only by owner of contract /// @param account Arbitrager account /// @param status Privilege status function updatePrivileged(address account, bool status) external; /// @notice Burns USC tokens from msg.sender and sends him WETH from reserves /// @param amount Amount of USC tokens to burn /// @param receiver Receiver of WETH /// @return ethAmount Amount of WETH received function burn(uint256 amount, address receiver) external returns (uint256 ethAmount); /// @notice Sets mint pause /// @param isPaused true of false function setMintPause(bool isPaused) external; /// @notice Sets burn pause /// @param isPaused true of false function setBurnPause(bool isPaused) external; }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; interface IArbitrageV5 { struct SwapParams { address reserveAsset; uint256 amountIn; uint256 minAmountOut; } error DeltaBiggerThanAmountReceivedETH(uint256 deltaETH, uint256 receivedETH); error ToleranceTooBig(uint16 _tolerance); error PriceSlippageTooBig(); error NotArbitrager(address account); error PriceIsNotPegged(); error ReserveDiffTooBig(); error ChiPriceNotPegged(uint256 spotPrice, uint256 twapPrice); error FeeTooBig(uint256 fee); error ChiSpotPriceTooBig(); error ContractIsPaused(); error ReserveTxLimitExceeded(); event SetPriceTolerance(uint16 priceTolerance); event Mint(address indexed account, address token, uint256 amount, uint256 uscAmount); event ExecuteArbitrage( address indexed account, uint256 indexed arbNum, uint256 deltaUsd, uint256 reserveDiff, uint256 ethPrice, uint256 rewardValue ); event UpdateArbitrager(address indexed account, bool status); event SetMaxMintBurnPriceDiff(uint256 maxMintBurnPriceDiff); event SetChiPriceTolerance(uint16 chiPriceTolerance); event SetMaxMintBurnReserveTolerance(uint16 maxBurnReserveTolerance); event SetMintBurnFee(uint256 mintFee); event UpdatePrivileged(address indexed privileged, bool isPrivileged); event Burn(address account, uint256 amount, uint256 reserveReceived, address reserve); event RewardUSC(uint256 amount); event SetReserveMintTxLimit(address reserveAsset, uint256 limit); event SetReserveBurnTxLimit(address reserveAsset, uint256 limit); /// @notice Sets absolute peg price tolerance /// @param _priceTolerance Absolute value of price tolerance /// @custom:usage This function should be called from owner in purpose of setting price tolerance function setPegPriceToleranceAbs(uint256 _priceTolerance) external; /// @notice Sets spot price tolerance from TWAP price /// @dev 100% = 10000 /// @param _priceTolerance Price tolerance in percents /// @custom:usage This function should be called from owner in purpose of setting price tolerance function setPriceTolerance(uint16 _priceTolerance) external; /// @notice Sets reserve tx limit for minting /// @param reserveAsset Address of reserve asset /// @param limit Limit of reserve tx function setReserveMintTxLimit(address reserveAsset, uint256 limit) external; /// @notice Sets reserve tx limit for burn /// @param reserveAsset Address of reserve asset /// @param limit Limit of reserve tx function setReserveBurnTxLimit(address reserveAsset, uint256 limit) external; /// @notice Mint USC tokens for ETH /// @dev If USC price is different from target price for less then max mint price diff, then minting is allowed without performing arbitrage /// @param receiver Receiver of USC tokens /// @return uscAmount Amount of USC tokens minted function mint(address receiver) external payable returns (uint256 uscAmount); /// @notice Mint USC tokens for WETH /// @param token Address of a token that will be used to mint USC /// @param amount Amount of token to use for minting /// @param receiver Receiver of USC tokens /// @return uscAmount Amount of USC tokens minted function mint(address token, uint256 amount, address receiver) external returns (uint256 uscAmount); /// @notice Executes arbitrage with reserve rebalance /// @param chiSpotPrice CHI spot price /// @param swapParams Array of swap params for reserve holder in order to perform safe reserve sell and get ETH /// @return amountOut Amount out /// @dev This function should execute arbitrage with reserve rebalance when there is not enough ETH in reserve holder so some reserves need to be sold function executeArbitrageWithReserveSell( uint256 chiSpotPrice, SwapParams[] memory swapParams ) external returns (uint256); /// @notice Executes arbitrage, profit sent to caller /// @notice Returns reward value in USD /// @param maxChiSpotPrice maximum spot price of CHI, if 0 TWAP check will be done /// @return rewardValue Reward value in USD /// @custom:usage This function should be called from external keeper in purpose of pegging USC price and getting reward /// @custom:usage This function has no restrictions, anyone can be arbitrager function executeArbitrage(uint256 maxChiSpotPrice) external returns (uint256 rewardValue); /// @notice Gets information for perfoming arbitrage such as price diff, reserve diff, discount /// @return isPriceAtPeg True if USC price is above target price /// @return isPriceAboveTarget True if USC price is above target price /// @return isExcessOfReserves True if there is excess of reserves /// @return reserveDiff Reserve diff, excess or deficit of reserves function getArbitrageData() external view returns (bool isPriceAtPeg, bool isPriceAboveTarget, bool isExcessOfReserves, uint256 reserveDiff); /// @notice Update arbitrager status /// @dev This function can be called only by owner of contract /// @param account Arbitrager account /// @param status Arbitrager status function updateArbitrager(address account, bool status) external; /// @notice Claim rewards from arbitrages /// @dev This function can be called only by owner of contract /// @param tokens Tokens to claim rewards for /// @param amounts Amounts of tokens to claim rewards for function claimRewards(IERC20[] memory tokens, uint256[] memory amounts) external; /// @notice Rewards USC /// @dev This function can be called only by owner of contract /// @param amount Amount of USC to reward function rewardUSC(uint256 amount) external; /// @notice Sets maximum mint and burn price difference /// @dev This function can be called only by owner of contract, value is absolute /// @param _maxMintBurnPriceDiff Maximum mint and burn price difference function setMaxMintBurnPriceDiff(uint256 _maxMintBurnPriceDiff) external; /// @notice Sets CHI price tolerance percentage when checking TWAP /// @dev This function can be called only by owner of contract, value is relative /// @param _chiPriceTolerance CHI price tolerance percentage function setChiPriceTolerance(uint16 _chiPriceTolerance) external; /// @notice Sets maximum mint and burn price difference /// @dev This function can be called only by owner of contract, value is relative /// @param _maxMintBurnReserveTolerance Maximum mint and burn reserve tolerance function setMaxMintBurnReserveTolerance(uint16 _maxMintBurnReserveTolerance) external; /// @notice Sets mint and burn fee /// @dev This function can be called only by owner of contract /// @param _mintBurnFee Mint and burn fee function setMintBurnFee(uint16 _mintBurnFee) external; /// @notice Update privilege status, only privileged accounts can call arbitrage and pass CHI TWAP check /// @dev This function can be called only by owner of contract /// @param account Arbitrager account /// @param status Privilege status function updatePrivileged(address account, bool status) external; /// @notice Burns USC tokens from msg.sender and sends him WETH from reserves /// @param amount Amount of USC tokens to burn /// @param reserveToReceive Address of reserve to receive /// @return ethAmount Amount of WETH received function burn(uint256 amount, address reserveToReceive) external returns (uint256 ethAmount); /// @notice Sets mint pause /// @param isPaused true of false function setMintPause(bool isPaused) external; /// @notice Sets burn pause /// @param isPaused true of false function setBurnPause(bool isPaused) external; }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; interface IBurnableERC20 { function burnFrom(address account, uint256 amount) external; }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "./IOracle.sol"; interface IChainlinkEthAdapter is IOracle { /// @notice Gets exchange rate for ETH from Chainlink price feed /// @return rate Exchange rate between underlying asset and ETH function exchangeRate() external view returns (uint256 rate); }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "./IToken.sol"; interface ICHI is IToken { /// @notice Burns given amount from given account /// @param account Account to burn CHI from /// @param amount Amount of CHI to burn /// @custom:usage This function should be called from OCHI contract in purpose of burning CHI to boost option discount function burnFrom(address account, uint256 amount) external; }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; interface IChiLocking { struct LockedPosition { uint256 amount; uint256 startEpoch; uint256 duration; // in epochs uint256 shares; uint256 withdrawnChiAmount; } struct LockingData { uint256 lastUpdatedEpoch; uint256 unclaimedStETH; LockedPosition[] positions; } struct AllLockedPositionsOutput { LockedPosition position; uint256 votingPower; uint256 stETHreward; uint256 totalAccumulatedChi; uint256 totalChiRewards; } struct EpochData { uint256 lockedSharesInEpoch; uint256 totalLockedChiInEpoch; uint256 sharesToUnlock; uint256 cumulativeStETHPerLockedShare; uint256 cumulativeStETHPerUnlocked; uint256 numberOfEndingPositions; } event SetUscStaking(address indexed uscStaking); event SetRewardController(address indexed rewardController); event SetChiLocker(address indexed chiLocker, bool indexed status); event LockChi(address indexed account, uint256 amount, uint256 shares, uint256 startEpoch, uint256 endEpoch); event UpdateEpoch( uint256 indexed epoch, uint256 totalLockedChi, uint256 chiEmissions, uint256 stETHrewards, uint256 stEthPerLockedShare ); event ClaimStETH(address indexed account, uint256 amount); event WithdrawChiFromAccount(address indexed account, address indexed toAddress, uint256 amount); error ZeroAmount(); error NotRewardController(); error NotChiLocker(); error UnavailableWithdrawAmount(uint256 amount); /// @notice Sets address of uscStaking contract /// @param _uscStaking Address of uscStaking contract function setUscStaking(address _uscStaking) external; /// @notice Sets address of rewardController contract /// @param _rewardController Address of rewardController contract function setRewardController(address _rewardController) external; /// @notice Sets address of contract who can call lock function /// @param contractAddress Address of contract who calles lock function, chiStaking currently /// @param toSet true if contract can call lock function, false otherwise function setChiLocker(address contractAddress, bool toSet) external; /// @notice Gets locked position for given account and position index /// @param account Account to get locked position for /// @param pos Index of locked position /// @return position Locked position function getLockedPosition(address account, uint256 pos) external view returns (LockedPosition memory position); /// @notice Gets all locked position for given account /// @param account Account to get locked positions /// @return out Array of locked positions function getAllLockedPositions(address account) external view returns (AllLockedPositionsOutput[] memory out); /// @notice Gets total staked chi amount, locked amount is also considered staked /// @return stakedChi Total staked chi amount function getStakedChi() external view returns (uint256 stakedChi); /// @notice Gets total locked chi amount /// @return lockedChi Total locked chi amount function getLockedChi() external view returns (uint256 lockedChi); /// @notice Gets total voting power /// @return totalVotingPower Total voting power function getTotalVotingPower() external view returns (uint256 totalVotingPower); /// @notice Gets total chi amount that is available to withdraw for given account /// @param account Account to get available chi amount for /// @return availableTotal Total amount of chi that is available to withdraw function availableChiWithdraw(address account) external view returns (uint256 availableTotal); /// @notice Locks given amount of chi for given account for given duration /// @param account Account to lock chi for /// @param amount Amount of chi to lock /// @param duration Duration of locking in epochs /// @custom:usage This function should be called from chiStaking and uscStaking contracts in purpose of locking chi function lockChi(address account, uint256 amount, uint256 duration) external; /// @notice Updates epoch data /// @param chiEmissions Amount of chi incentives for chi lockers that is emitted in current epoch /// @param stETHrewards Amount of stETH rewards for chi lockers that is emitted in current epoch /// @custom:usage This function should be called from rewardController contract in purpose of updating epoch data function updateEpoch(uint256 chiEmissions, uint256 stETHrewards) external; /// @notice Claims stETH rewards for given account /// @notice This contract does not send stETH rewards nor holds them, reserveHolder does that /// @notice This contract only calculates and updates unclaimed stETH amount for given account /// @param account Account to claim stETH rewards for /// @return amount Amount of stETH rewards that user can claim /// @custom:usage This function should be called from rewardController contract in purpose of claiming stETH rewards function claimStETH(address account) external returns (uint256 amount); /// @notice Withdraws given amount of unlocked chi tokens for given account, sends to account by default /// @notice This contract hold CHI tokens and inside this function sends them back to user /// @param account Account to withdraw CHI for /// @param amount Amount of CHI tokens to withdraw /// @custom:usage This function should be called from chiStaking contract in purpose of withdrawing CHI tokens function withdrawChiFromAccount(address account, uint256 amount) external; /// @notice Withdraws given amount of unlocked chi tokens for given account, sends to account by default /// @notice This contract hold CHI tokens and inside this function sends them back to user /// @param account Account to withdraw CHI for /// @param toAddress Address to which to send tokens /// @param amount Amount of CHI tokens to withdraw /// @custom:usage This function should be called from chiStaking contract in purpose of withdrawing CHI tokens function withdrawChiFromAccountToAddress(address account, address toAddress, uint256 amount) external; /// @notice Calculates and returns unclaimed stETH amount for given account /// @param account Account to calculate unclaimed stETH amount for /// @return totalAmount Total amount of unclaimed stETH for given account function unclaimedStETHAmount(address account) external view returns (uint256 totalAmount); /// @notice Calculates and returns voting power for given account /// @param account Account to calculate voting power for /// @return votingPower Voting power for given account function getVotingPower(address account) external view returns (uint256 votingPower); }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import {IChiLocking} from "./IChiLocking.sol"; import {IStaking} from "./IStaking.sol"; interface IChiStaking is IStaking { event Lock(address indexed account, uint256 amount, uint256 duration, bool useStakedTokens); event ClaimStETH(address indexed account, uint256 amount); event SetChiLocking(address indexed chiLocking); event SetRewardController(address indexed rewardController); error InvalidDuration(uint256 duration); /// @notice Sets address of chiLocking contract /// @param _chiLocking Address of chiLocking contract function setChiLocking(IChiLocking _chiLocking) external; /// @notice Updates epoch data /// @param stETHrewards Amount of stETH rewards for chi stakers that is emitted in current epoch /// @custom:usage This function should be called from rewardController contract in purpose of updating epoch data function updateEpoch(uint256 stETHrewards) external; /// @notice Locks given amount of chi tokens for given duration for caller /// @dev If caller want to use staked tokens for locking, function will unstake them first /// @param amount Amount of chi tokens to lock /// @param duration Locking duration in epochs /// @param useStakedTokens If true, then staked tokens will be used for locking function lock(uint256 amount, uint256 duration, bool useStakedTokens) external; }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; interface IChiVesting { struct VestingData { uint256 startAmount; uint256 shares; uint256 unlockedChi; uint256 lastWithdrawnEpoch; uint256 unclaimedStETH; uint256 lastClaimedEpoch; } struct EpochData { uint256 cumulativeStETHRewardPerShare; uint256 cumulativeUnlockedPerShare; } event AddVesting(address indexed account, uint256 amount, uint256 shares); event UpdateEpoch(uint256 indexed epoch, uint256 stETHrewards, uint256 totalLockedChi); event WithdrawChi(address indexed account, uint256 amount); event ClaimStETH(address indexed account, uint256 amount); event SetRewardController(address indexed rewardController); event SetChiVester(address indexed chiVester, bool indexed toSet); error NotRewardController(); error NotChiVester(); error CliffPassed(); error UnavailableWithdrawAmount(uint256 amount); /// @notice Gets cliff duration /// @return duration Cliff duration function cliffDuration() external view returns (uint256 duration); /// @notice Sets address of rewardController contract /// @param rewardController Address of rewardController contract function setRewardController(address rewardController) external; /// @notice Updates status of contract that can add vesting, TimeWeightedBonding contract in this case /// @param contractAddress Address of contract /// @param toSet Status to set function setChiVester(address contractAddress, bool toSet) external; /// @notice Gets total locked chi amount /// @return lockedChi Total locked chi amount function getLockedChi() external view returns (uint256 lockedChi); /// @notice Vests given amount of chi tokens for given account /// @param account Account to vest tokens for /// @param chiAmount Amount of chi tokens to vest /// @custom:usage This function should be called from TimeWeightedBonding contract in purpose of vesting chi tokens function addVesting(address account, uint256 chiAmount) external; /// @notice Updates epoch data /// @param chiEmissions Amount of chi incentives for vesters in current epoch /// @param stETHrewards Amount of stETH rewards for vesters that is emitted in current epoch /// @custom:usage This function should be called from rewardController contract in purpose of updating epoch data function updateEpoch(uint256 chiEmissions, uint256 stETHrewards) external; /// @notice Withdraws vested chi tokens for caller /// @dev Contract hold vested chi tokens and inside this function it transfers them to caller /// @param amount Amount of chi tokens to withdraw function withdrawChi(uint256 amount) external; /// @notice Claims stETH rewards for given account /// @notice This contract does not send stETH rewards nor holds them, reserveHolder does that /// @notice This contract only calculates and updates unclaimed stETH amount for given account /// @param account Account to claim stETH rewards for /// @return amount Amount of stETH rewards that user can claim /// @custom:usage This function should be called from rewardController contract in purpose of claiming stETH rewards function claimStETH(address account) external returns (uint256 amount); /// @notice Calculates and returns unclaimed stETH rewards for given account /// @param account Account to calculate unclaimed stETH rewards for /// @return amount Amount of unclaimed stETH rewards function unclaimedStETHAmount(address account) external view returns (uint256 amount); /// @notice Calculates and returns voting power earned from vesting for given account /// @param account Account to calculate voting power for /// @return votingPower Voting power earned from vesting function getVotingPower(address account) external view returns (uint256 votingPower); /// @notice Gets total voting power /// @return totalVotingPower Total voting power function getTotalVotingPower() external view returns (uint256 totalVotingPower); /// @notice Calculates and returns chi amount that is available for withdrawing for given account /// @param account Account to calculate available chi amount for /// @return availableChi Total amount of chi that is available for withdrawing function availableChiWithdraw(address account) external view returns (uint256 availableChi); }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; interface ICurvePool { /// @notice Swaps tokens /// @param i Index of token to swap from /// @param j Index of token to swap to /// @param dx Amount of tokens to swap /// @param min_dy Minimum amount of tokens to receive /// @return dy Amount of tokens received function exchange(int128 i, int128 j, uint256 dx, uint256 min_dy) external payable returns (uint256 dy); }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; interface IEtherFiLiquidityPool { function deposit() external payable returns (uint256); }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; interface IIDO { error IDONotRunning(); error IDOAlreadyStarted(); error MinValueNotReached(); error MaxValueReached(); error SoftCapReached(); error SoftCapNotReached(); error IDONotFinished(); error ClaimingNotEnabled(); error AlreadyClaimed(); error AmountLargerThenBought(uint256 amount, uint256 ethAmount); error EtherSendFailed(address to, uint256 amount); event Buy(address indexed account, uint256 amount, uint256 totalEthAmount); event Withdraw(address indexed account, uint256 amount, uint256 ethAmountLeft); event Claim(address indexed account, uint256 ethAmount, uint256 chiAmount); /// @notice Buys CHI token on IDO for the listed price by sending ETH function buy() external payable; /// @notice Withdraws deposited ETH if soft cap is not reached function withdraw() external; /// @notice Claims bought CHI once the IDO is finished /// @notice Bought CHI is vested function claim() external; /// @notice Returns how much CHI would user get at this moment for deposited ETH amount /// @param account account for which to return amount /// @param ethDeposit deposited ETH amount /// @return chiAmount resulting CHI amount function calculateChiAmountForAccount(address account, uint256 ethDeposit) external view returns (uint256 chiAmount); }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; interface ILidoPool { function submit(address _referral) external payable returns (uint256); }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import {RewardTokenData, RewardTokenConfig} from "../types/DataTypes.sol"; interface ILockingManager { error EpochNotStartedYet(); error InvalidLockPeriod(); error NoRewardsToClaim(); error InvalidLockIndex(); error LockPeriodNotOver(); event LockCreated(address indexed user, uint256 amount, uint256 lockPeriod); event TokensUnlockedAllPositions(address indexed user, uint256 amount); event TokensUnlocked(address indexed user, uint256 amount, uint256 startEpoch, uint256 endEpoch); event RewardsClaimed(address indexed user, uint256 amount); event EpochUpdated(uint256 newEpoch); event RewardsPerEpochUpdated(uint256 newRewardsPerEpoch); function lock(uint256 amount, uint256 lockPeriod) external; function withdraw(uint256 lockIndex) external; function withdrawAllUnlockedPositions() external; function claimRewards() external; function updateEpoch() external; function updateRewardsPerEpoch(uint256 newRewardsPerEpoch) external; function getUserBalance(address user) external view returns (uint256); }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; interface ILPRewards { struct LockingTokenData { uint256 amountLocked; uint64 lastClaimedEpoch; uint64 endingEpoch; } struct EpochData { int256 totalDeltaAmountLocked; int256 cumulativeProfit; } event LockLP(uint256 indexed lockingTokenId, uint256 amount, uint64 currentEpoch, uint64 endingEpoch); event ClaimRewards(uint256 indexed lockingTokenId, address indexed account, uint256 amount, int256 rewardUSD); event UpdateEpoch(uint64 indexed epoch, uint256 lpValue, int256 totalAmountLocked, int256 profitPerToken); event RecoverLPTokens(address indexed account, uint256 amount); event SetOCHI(address indexed ochi); error LockingTokenIdAlreadyUsed(uint256 lockingTokenId); error ClaimngRewardLessThanZero(); error NotOCHI(); /// @notice Gets current epoch /// @return currentEpoch Current epoch function currentEpoch() external view returns (uint64 currentEpoch); /// @notice Sets address of OCHI contract /// @param _ochi Address of OCHI contract /// @custom:usage This function should be called only once during deployment /// @custom:usage Caller must be owner function setOCHI(address _ochi) external; /// @notice Locks LP tokens for given period, user gets rewards until the end of the period /// @param lockingTokenId Unique OCHI id /// @param amount Amount of LP tokens to lock /// @param epochDuration Locking duration in epochs /// @custom:usage This function should be called from OCHI contract in purpose of locking LP tokens function lockLP(uint256 lockingTokenId, uint256 amount, uint64 epochDuration) external; /// @notice Claims rewards for given token id /// @param lockingTokenId Unique OCHI id /// @param account Account to send rewards to /// @custom:usage This function should be called from OCHI contract in purpose of claiming rewards function claimRewards(uint256 lockingTokenId, address account) external; /// @notice End current epoch and start new one /// @custom:usage This function should be called from OCHI contract when epoch is updated function updateEpoch() external; /// @notice Takes LP tokens from contract and sends them to receiver /// @param receiver Account to send LP tokens to /// @custom:usage This function should be called only in case of moving liquidity to another pool function recoverLPTokens(address receiver) external; /// @notice Calculates unclaimed rewards for given token id /// @param lockingTokenId Unique OCHI id /// @return rewardUSD Amount of unclaimed rewards in USD function calculateUnclaimedReward(uint256 lockingTokenId) external view returns (int256 rewardUSD); /// @notice returns profit in USD for the last epoch /// @return profit profit in USD function getLastEpochProfit() external view returns (int256 profit); }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import {IStakingWithEpochs} from "./IStakingWithEpochs.sol"; import {IStaking} from "./IStaking.sol"; interface ILPStaking is IStakingWithEpochs, IStaking { event UpdateEpoch(uint256 indexed epoch, uint256 chiEmissions); event LockChi(address indexed account, uint256 amount, uint256 duration); event ClaimStETH(address indexed account, uint256 amount); error InvalidDuration(uint256 duration); function updateEpoch(uint256, uint256) external; function lockChi(uint256) external; }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; interface IMintable { error ZeroAddress(); error NotMinter(address _caller); event UpdateMinter(address indexed _account, bool indexed _status); /// @notice Checks if account is minter /// @param account Address to check /// @return status Status of minter, true if minter, false if not function isMinter(address account) external view returns (bool status); /// @notice Grants/revokes minter role /// @param account Address to grant/revoke minter role /// @param status True to grant, false to revoke function updateMinter(address account, bool status) external; }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; interface IMintableBurnable is IERC20 { function mint(address to, uint256 amount) external; function burnFrom(address from, uint256 amount) external; }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; interface IMintableERC20 is IERC20 { function mint(address account, uint256 amount) external; }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; interface IOCHI { struct ChiOption { uint256 amount; uint256 strikePrice; uint256 uscEthPairAmount; uint256 chiEthPairAmount; uint64 lockedUntil; uint64 validUntil; } event Mint( uint256 indexed tokenId, uint256 chiAmount, uint256 uscEthPairAmount, uint256 chiEthPairAmount, uint64 lockPeriodInEpochs, uint256 strikePrice, uint256 oChiAmount ); event Burn(address indexed account, uint256 indexed tokenId, uint256 chiAmount); event UpdateEpoch(uint64 indexed epoch, uint256 timestamp); event ClaimRewardsOCHI(uint256 indexed tokenId); event RecoverLPTokens(); error PolTargetRatioExceeded(); error InvalidLockPeriod(uint256 lockPeriod); error NotAllowed(uint256 tokenId); error OptionLocked(uint256 tokenId); error OptionExpired(uint256 tokenId); error EpochNotFinished(); error MintPaused(); error BurnPaused(); error ClaimRewardsPaused(); /// @notice Pauses minting /// @param _pauseMint Pause minting /// @custom:usage This function should be called only from owner function setPauseMint(bool _pauseMint) external; /// @notice Pauses burning /// @param _pauseBurn Pause burning /// @custom:usage This function should be called only from owner function setPauseBurn(bool _pauseBurn) external; /// @notice Pauses claiming rewards /// @param _pauseClaimRewards Pause claiming rewards /// @custom:usage This function should be called only from owner function setPauseClaimRewards(bool _pauseClaimRewards) external; /// @notice Mint OCHI tokens for given period, user gets rewards until the end of the period /// @param chiAmount Amount of CHI tokens user wants to burn in order to boost his discount in option /// @param uscEthPairAmount Amount of USC/ETH LP tokens user wants to lock and sell to protocol /// @param chiEthPairAmount Amount of CHI/ETH LP tokens user wants to lock and sell to protocol /// @param lockPeriodInEpochs Locking duration in epochs function mint( uint256 chiAmount, uint256 uscEthPairAmount, uint256 chiEthPairAmount, uint64 lockPeriodInEpochs ) external; /// @notice Burn OCHI token in order to execute option and get his discounted CHI /// @notice When lock period expires user has lock period window to execute option, after that option can not be executed /// @param tokenId Unique OCHI id function burn(uint256 tokenId) external; /// @notice Ends current epoch and start new one function updateEpoch() external; /// @notice Claims rewards for given token id /// @param tokenId Unique OCHI id function claimRewards(uint256 tokenId) external; /// @notice Returns unclaimed rewards value for given token id /// @param tokenId Unique OCHI id /// @return rewardsUSD rewards value in USD function getUnclaimedRewardsValue(uint256 tokenId) external view returns (int256 rewardsUSD); /// @notice Recovers LP tokens from rewards contract /// @custom:usage This function should be called only in case of moving liquidity to another pool function recoverLPTokens() external; function calculateOptionData( uint256 chiAmount, uint256 uscEthPairAmount, uint256 chiEthPairAmount, uint256 lockPeriodInEpochs ) external view returns (uint256 strikePrice, uint256 oChiAmount); function getAndValidatePositionsData( uint256 uscEthPairAmount, uint256 chiEthPairAmount ) external view returns (uint256 multiplier, uint256 value); function getLastEpochTotalReward() external view returns (int256 totalReward); function claimAllRewards() external; }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; /// @title IOptimismMintableERC20 /// @notice This interface is available on the OptimismMintableERC20 contract. /// We declare it as a separate interface so that it can be used in /// custom implementations of OptimismMintableERC20. interface IOptimismMintableERC20 is IERC165 { function remoteToken() external view returns (address); function bridge() external returns (address); function mint(address _to, uint256 _amount) external; function burn(address _from, uint256 _amount) external; }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; interface IOracle { /// @notice Gets name of price adapter /// @return name Name of price adapter function name() external view returns (string memory name); /// @notice Gets decimals of price adapter /// @return decimals Decimals of price adapter function decimals() external view returns (uint8 decimals); /// @notice Gets price base token from Chainlink price feed /// @return price price in USD for the 1 baseToken function peek() external view returns (uint256 price); }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "contracts/interfaces/IOracle.sol"; interface IPriceFeedAggregator { event SetPriceFeed(address indexed base, address indexed feed); error ZeroAddress(); /// @notice Sets price feed adapter for given token /// @param base Token address /// @param feed Price feed adapter address function setPriceFeed(address base, address feed) external; /// @notice Gets price feed adapter for given token /// @param base Token address /// @return feed Price feed adapter address function priceFeeds(address base) external view returns (IOracle feed); /// @notice Gets price for given token /// @param base Token address /// @return price Price for given token function peek(address base) external view returns (uint256 price); }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; interface IReserveHolder { event SetArbitrager(address indexed arbitrager, bool enabled); event SetClaimer(address indexed claimer); event SetEthThreshold(uint256 threshold); event SetSwapEthTolerance(uint256 tolerance); event SetCurveStEthSafeGuardPercentage(uint256 percentage); event Deposit(address indexed account, uint256 amount); event Rebalance(uint256 ethAmount, uint256 stEthAmount); event Redeem(address indexed account, uint256 amount); event RedeemSwap(uint256 ethAmount, uint256 stEthAmount); event ClaimRewards(address indexed account, uint256 amount); event Receive(address indexed account, uint256 amount); error NotArbitrager(address _account); error NotClaimer(address _account); error ThresholdTooHigh(uint256 _threshold); error SafeGuardTooHigh(uint256 _safeGuard); error EtherSendFailed(address _account, uint256 _amount); /// @notice Updates arbitrager status /// @param arbitrager Arbitrager address /// @param status Arbitrager status function setArbitrager(address arbitrager, bool status) external; /// @notice Sets claimer address /// @param claimer Claimer address /// @custom:usage Claimer should be rewardController contract function setClaimer(address claimer) external; /// @notice Sets eth threshold /// @param ethThreshold Eth threshold /// @custom:usage Eth threshold should be set in percentage /// @custom:usage Part of reserves is in WETH so arbitrage contract can use them without swapping stETH for ETH function setEthThreshold(uint256 ethThreshold) external; /// @notice Sets swap eth tolerance /// @param swapEthTolerance Swap eth tolerance /// @custom:usage Swap eth tolerance should be set in wei /// @custom:usage Absolute tolerance for swapping stETH for ETH function setSwapEthTolerance(uint256 swapEthTolerance) external; /// @notice Sets curve stETH safe guard percentage /// @param curveStEthSafeGuardPercentage Curve stETH safe guard percentage function setCurveStEthSafeGuardPercentage(uint256 curveStEthSafeGuardPercentage) external; /// @notice Gets reserve value in USD /// @return reserveValue Reserve value in USD function getReserveValue() external view returns (uint256 reserveValue); /// @notice Gets current rewards generated by stETH /// @return currentRewards Current rewards generated by stETH function getCurrentRewards() external view returns (uint256 currentRewards); /// @notice Gets cumulative rewards generated by stETH /// @return cumulativeRewards Cumulative rewards generated by stETH function getCumulativeRewards() external view returns (uint256 cumulativeRewards); /// @notice Deposits stETH to reseves /// @param amount Amount of stETH to deposit function deposit(uint256 amount) external; /// @notice Rebalance reserve in order to achieve balace/ethThreshold ratio /// @dev If there is more WETH than ethThreshold then unwrap WETH and get stETH from Lido /// @dev If there is less WETH than ethThreshold then swap stETH for WETH on UniV2 /// @custom:usage This function should be called by external keeper function rebalance() external; /// @notice Redeems stETH from reserves /// @param amount Amount of stETH to redeem /// @return wethAmount Amount of WETH received /// @custom:usage This function should be called by arbitrage contract function redeem(uint256 amount) external returns (uint256 wethAmount); /// @notice Claims stETH rewards in given amount for given account /// @notice Contract does not perform any check and is relying on rewardController contract to perform them /// @param account Account to claim stETH rewards for /// @param amount Amount of stETH to claim /// @custom:usage This function should be called by rewardController contract function claimRewards(address account, uint256 amount) external; /// @notice Wrapps ETH to WETH /// @dev Users can buy USC with ETH which is transfered to this contract. This function should be called to wrapp than ETH to WETH /// @custom:usage This function should be called by external keeper function wrapETH() external; }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; interface IReserveHolderV2 { struct ReserveAssetInfo { /// @dev Amount of underlying ETH for this LST /// @dev For rebasing tokens it will be equal to amount of LST but for non-rebasing tokens it will depends on exchange rate uint256 totalDeposited; /// @dev Amount of LST claimed for this LST uint256 totalClaimed; /// @dev Address of underlying asset, if LST is rebasing then underlying asset is that LST but if LST is non-rebasing then underlying asset is WETH address underlyingAsset; /// @dev Address of swap adapter for this LST address swapAdapter; /// @dev Percentage of total reserve tokens that should be kept in this underlying asset uint256 percentage; } event SetArbitrager(address indexed arbitrager, bool enabled); event SetClaimer(address indexed claimer); event SetRebalancer(address indexed rebalancer, bool enabled); event SetSwapTolerance(uint256 swapTolerance); event AddReserveAsset(address indexed reserveAsset); event SetReserveAssetAdapter(address indexed reserveAsset, address adapter); event SetReserveAssetPercentage(address indexed reserveAsset, uint256 percentage); event Deposit(address indexed account, address indexed reserveAsset, uint256 amount); event Rebalance(); event Redeem(address indexed account, address indexed reserve, uint256 amount); event ClaimRewards(address indexed account); event Claimed(address indexed account, address indexed reserveAsset, uint256 amount); error NotArbitrager(); error NotClaimer(); error NotRebalancer(); error PercentageTooHigh(); error AssetAlreadyAdded(); /// @notice Gets reserve assets /// @return reserveAssets Reserve assets function getReserveAssets() external view returns (address[] memory reserveAssets); /// @notice Updates arbitrager status /// @param arbitrager Arbitrager address /// @param status Arbitrager status function setArbitrager(address arbitrager, bool status) external; /// @notice Sets claimer address /// @param claimer Claimer address /// @custom:usage Claimer should be rewardController contract function setClaimer(address claimer) external; /// @notice Sets rebalancer address /// @param rebalancer Rebalancer address /// @param status Rebalancer status function setRebalancer(address rebalancer, bool status) external; /// @notice Sets swap tolerance /// @param swapTolerance Swap tolerance /// @dev Only owner can call this function function setSwapTolerance(uint256 swapTolerance) external; /// @notice Adds reserve asset to reserves /// @param reserveAsset Reserve asset address /// @param reserveAdapter Adapter address /// @param reservePercentage Percentage of reserve asset in reserves /// @dev Only owner can call this function function addReserveAsset(address reserveAsset, address reserveAdapter, uint256 reservePercentage) external; /// @notice Sets adapter for new reserve asset /// @param reserveAsset Reserve asset address /// @param adapter Adapter address /// @dev Only owner can call this function function setReserveAssetAdapter(address reserveAsset, address adapter) external; /// @notice Sets percentage of reserve asset in reserves /// @param reserveAsset Reserve asset address /// @param percentage Percentage of reserve asset in reserves function setReserveAssetPercentage(address reserveAsset, uint256 percentage) external; /// @notice Gets reserve value in USD /// @return reserveValue Reserve value in USD function getReserveValue() external view returns (uint256 reserveValue); /// @notice Deposits reserve asset to reserves /// @param reserveAsset Reserve asset address /// @param amount Amount of reserve asset to deposit function deposit(address reserveAsset, uint256 amount) external; /// @notice Rebalance reserve in order to achieve balace /// @param spotPrices Spot prices of reserve assets. This is used for slippage protection /// @custom:usage This function should be called by external keeper function rebalance(uint256[] memory spotPrices) external; /// @notice Redeems stETH from reserves /// @param amount Amount of stETH to redeem /// @param reserve Reserve to redeem /// @return wethAmount Amount of WETH received /// @custom:usage This function should be called by arbitrage contract function redeem(uint256 amount, address reserve) external returns (uint256 wethAmount); /// @notice Claims stETH rewards in given amount for given account /// @notice Contract does not perform any check and is relying on rewardController contract to perform them /// @param account Account to claim stETH rewards for /// @custom:usage This function should be called by rewardController contract function claimRewards(address account) external; /// @notice Swap reserve asset for ETH /// @param reserve Reserve asset address /// @param amountIn Amount of reserve asset to swap /// @param minAmountOut Minimum amount of ETH to receive /// @custom:usage This function should be called by arbitrage contract in order to swap reserve asset for ETH and execute arbitrage with ETH function swapReserveForEth(address reserve, uint256 amountIn, uint256 minAmountOut) external; }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import {IArbitrage} from "./IArbitrage.sol"; interface IRewardController { struct EpochData { uint256 totalUscReward; uint256 reserveHolderTotalRewards; } struct StETHRewards { uint256 uscStakingStEthReward; uint256 chiStakingStEthReward; uint256 chiLockingStEthReward; uint256 chiVestingStEthReward; uint256 uscEthLPStakingStEthReward; uint256 chiEthLPStakingStEthReward; } struct ChiIncentives { uint256 uscStakingChiIncentives; uint256 chiLockingChiIncentives; uint256 chiVestingChiIncentives; } event RewardUSC(address indexed account, uint256 amount); event UpdateEpoch(uint256 indexed epoch, uint256 totalStEthReward, uint256 totalChiIncentives); event ClaimStEth(address indexed account, uint256 amount); event SetChiIncentivesPerEpoch(uint256 indexed chiIncentivesPerEpoch); event SetArbitrager(address indexed arbitrager); event UpdateArbitrager(address indexed account, bool isArbitrager); error ZeroAmount(); error NotArbitrager(); error EpochNotFinished(); error StEthYieldPercentagesNotCorrect(); /// @notice Sets percentages of stETH rewards for each staking, locking and vesting contract /// @param _uscStakingStEthPercentage Percentage of stETH rewards for USC staking contract /// @param _chiStakingStEthPercentage Percentage of stETH rewards for CHI staking contract /// @param _chiLockingStEthPercentage Percentage of stETH rewards for CHI locking contract /// @param _chiVestingStEthPercentage Percentage of stETH rewards for CHI vesting contract /// @param _uscEthLPStakingStEthPercentage Percentage of stETH rewards for USC-ETH LP staking contract /// @param _chiEthLPStakingStEthPercentage Percentage of stETH rewards for CHI-ETH LP staking contract /// @dev Percentages should be in range 0-100, and sum of all percentages should be 100, 100% = 10000 /// @dev Only owner can call this function function setStEthPercentages( uint256 _uscStakingStEthPercentage, uint256 _chiStakingStEthPercentage, uint256 _chiLockingStEthPercentage, uint256 _chiVestingStEthPercentage, uint256 _uscEthLPStakingStEthPercentage, uint256 _chiEthLPStakingStEthPercentage ) external; /// @notice Set amount of chi incentives per epoch for chi lockers /// @param _chiIncentivesForChiLocking Amount of chi incentives per epoch function setChiIncentivesForChiLocking(uint256 _chiIncentivesForChiLocking) external; /// @notice Set amount of chi incentives per epoch for USC staking /// @param _chiIncentivesForUscStaking Amount of chi incentives per epoch function setChiIncentivesForUscStaking(uint256 _chiIncentivesForUscStaking) external; /// @notice Set amount of chi incentives per epoch for USC-ETH LP staking contracts /// @param _chiIncentivesForUscEthLPStaking Amount of chi incentives per epoch function setChiIncentivesForUscEthLPStaking(uint256 _chiIncentivesForUscEthLPStaking) external; /// @notice Set amount of chi incentives per epoch for CHI-ETH LP staking contracts /// @param _chiIncentivesForChiEthLPStaking Amount of chi incentives per epoch function setChiIncentivesForChiEthLPStaking(uint256 _chiIncentivesForChiEthLPStaking) external; /// @notice Sets arbitrager contract, deprecated in favor of updateArbitrager /// @param _arbitrager Arbitrager contract function setArbitrager(IArbitrage _arbitrager) external; /// @notice Sets arbitrager contract /// @param account Account to update arbitrager status for /// @param status True if account is arbitrager, false otherwise function updateArbitrager(address account, bool status) external; /// @notice Freezes given amount of USC token /// @dev Frozen tokens are not transfered they are burned and later minted again when conditions are met /// @param amount Amount of USC tokens to freeze /// @custom:usage This function should be called from Arbitrager contract in purpose of freezing USC tokens function rewardUSC(uint256 amount) external; /// @notice Updates epoch data /// @dev This functio will update epochs in all subcontracts and will distribute chi incentives and stETH rewards /// @custom:usage This function should be called once a week in order to end current epoch and start new one /// @custom:usage Thsi function ends current epoch and distributes chi incentives and stETH rewards to all contracts in this epoch function updateEpoch() external; /// @notice Claims stETH rewards for caller /// @dev This function will claim stETH rewards from all subcontracts and will send them to caller /// @dev Thsi contract does not hold stETH, instead it sends it through reserveHolder contract function claimStEth() external; /// @notice Calculates and returns unclaimed stETH amount for given account in all subcontracts /// @param account Account to calculate unclaimed stETH amount for /// @return totalAmount Total amount of unclaimed stETH for given account function unclaimedStETHAmount(address account) external view returns (uint256 totalAmount); }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; interface IStakedToken is IERC20 { function mint(address to, uint256 amount) external; function burn(address from, uint256 amount) external; }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import {IStakingWithEpochs} from "./IStakingWithEpochs.sol"; interface IStaking is IStakingWithEpochs { /// @notice Sets address of rewardController contract /// @param rewardController Address of rewardController contract function setRewardController(address rewardController) external; /// @notice Gets total staked chi amount /// @return stakedChi Total staked chi amount function getStakedChi() external view returns (uint256 stakedChi); /// @notice Claims stETH rewards for given account /// @notice This contract does not send stETH rewards nor holds them, reserveHolder does that /// @notice This contract only calculates and updates unclaimed stETH amount for given account /// @param account Account to claim stETH rewards for /// @return amount Amount of stETH rewards that user can claim /// @custom:usage This function should be called from rewardController contract in purpose of claiming stETH rewards function claimStETH(address account) external returns (uint256 amount); /// @notice Calculates and returns unclaimed stETH rewards for given account /// @param account Account to calculate unclaimed stETH rewards for /// @return amount Amount of unclaimed stETH rewards function unclaimedStETHAmount(address account) external view returns (uint256 amount); }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import {RewardTokenData, RewardTokenConfig} from "../types/DataTypes.sol"; interface IStakingManager { /// @notice Error emitted when staking for token is not started error StakingNotStarted(); /// @notice Error emitted when staking for token has already started error StakingAlreadyStarted(); /// @notice Error emitted when someone tries to call transfer hook /// @dev Transfer hook should be called only by StakedToken contract error NotStakedToken(); /// @notice Emitted when implementation of staked token is set /// @param implementation Address of new implementation contract event SetStakedTokenImplementation(address indexed implementation); /// @notice Emitted when locking manager is set for specific staking token /// @param stakingToken Staking token for which locking manager is set /// @param lockingManager Address of locking manager event SetLockingManager(address indexed stakingToken, address indexed lockingManager); /// @notice Emitted when staking for given token is started /// @param asset Token that staking has started for event StartStaking(address indexed asset); /// @notice Emitted when reward token is configured for specific staking token /// @param stakingToken Staking token for which reward token is configured /// @param rewardToken Reward token that is configured /// @param startTimestamp Start timestamp of reward program /// @param endTimestamp End timestamp of reward program /// @param emissionPerSecond Emission of reward token event ConfigureRewardToken( address indexed stakingToken, address indexed rewardToken, uint256 startTimestamp, uint256 endTimestamp, uint256 emissionPerSecond ); /// @notice Emitted when stake happens /// @param stakingToken Token that was staked /// @param from Account that staked /// @param recipient Account that staked for /// @param amount Amount staked event Stake(address indexed stakingToken, address indexed from, address indexed recipient, uint256 amount); /// @notice Emitted when withdraw happens /// @param stakingToken Token that was withdrawn /// @param from Account that withdrew /// @param recipient Account received the withdrawal /// @param amount Amount withdrawn event Unstake(address indexed stakingToken, address indexed from, address indexed recipient, uint256 amount); /// @notice Emitted when rewards are claimed for specific token /// @param from Account that claimed the rewards /// @param recipient Account that claimed the rewards for /// @param stakingToken Staking token for which rewards are claimed /// @param rewardToken Token for which rewards are claimed event ClaimRewardsForToken( address indexed from, address indexed recipient, address indexed stakingToken, address rewardToken ); /// @notice Gets address of staked token smart contract implementation /// @param implementation Address of implementation contract function getStakedTokenImplementation() external view returns (address implementation); /// @notice Returns staked token for given staking token /// @param stakingToken Staking token to get staked token for /// @param stakedToken Staked token for given staking token function getStakedToken(address stakingToken) external view returns (address stakedToken); /// @notice Returns is staking for given token started /// @param asset Token to check if staking has started for /// @param isStarted Is staking started function isStakingStarted(address asset) external view returns (bool isStarted); /// @notice Returns list of all staking tokens /// @param stakingTokens List of all staking tokens function getStakingTokens() external view returns (address[] memory stakingTokens); /// @notice Returns list of reward tokens for given staking token /// @param stakingToken Staking token to get reward tokens for /// @param rewardTokens List of reward tokens function getRewardTokens(address stakingToken) external view returns (address[] memory rewardTokens); /// @notice Returns reward token config for given staking and reward token /// @param stakingToken Staking token to get reward token config for /// @param rewardToken Reward token to get config for /// @param rewardTokenConfig Reward token config function getRewardTokenConfig( address stakingToken, address rewardToken ) external view returns (RewardTokenConfig memory rewardTokenConfig); /// @notice Returns reward token data for given staking and reward token, last updated timestamp and reward per staked token /// @param stakingToken Staking token to get data for /// @param rewardToken Reward token to get data for /// @param rewardTokenData Data for given reward token function getRewardTokenData( address stakingToken, address rewardToken ) external view returns (RewardTokenData memory rewardTokenData); /// @notice Returns total staked amount for given staking token /// @param stakingToken Staking token to get total staked amount for /// @param totalStaked Total staked amount for given staking token function getTotalStaked(address stakingToken) external view returns (uint256 totalStaked); /// @notice Returns user balance on locking manager /// @param user Account to get balance for /// @param stakingToken Staking token to get balance for /// @param balance Balance of user on locking manager function getUserBalanceOnLockingManager(address user, address stakingToken) external view returns (uint256 balance); /// @notice Returns total staked amount for given account and staking token /// @param user Account to get staked amount for /// @param stakingToken Staking token to get staked amount for /// @param stakedBalance Staked balance for given account and staking token function getUserStakedBalance(address user, address stakingToken) external view returns (uint256 stakedBalance); /// @notice Returns accrued rewards for given account, staking token and reward token /// @param user Account to get rewards for /// @param stakingToken Staking token to get rewards for /// @param rewardToken Reward token to get rewards for /// @param accruedRewards Accrued rewards for given account and reward token /// @dev Returned value does not represent total rewards, but only rewards that user had at the time of last user interaction with the contract function getUserAccruedRewardsForToken( address user, address stakingToken, address rewardToken ) external view returns (uint256 accruedRewards); /// @notice Returns total rewards for given account, staking token and reward token /// @param user Account to get rewards for /// @param stakingToken Staking token to get rewards for /// @param rewardToken Reward token to get rewards for /// @param totalRewards Total rewards for given account and reward token /// @dev Returned value represents total rewards that user has in the moment of calling this function /// @dev This function is used internally to calculate rewards for user when claiming rewards and externaly to show total rewards for user function getUserTotalRewardsForToken( address user, address stakingToken, address rewardToken ) external view returns (uint256 totalRewards); /// @notice Sets address of staked token implementation contract /// @param implementation Address of implementation contract /// @dev When this address is changed all staked token smart contract will be upgraded /// @dev Can be called only by owner function setStakedTokenImplementation(address implementation) external; /// @notice Sets address of locking manager for given staking token /// @param stakingToken Staking token to set locking manager for /// @param lockingManager Address of locking manager /// @dev Locking manager is used to lock staked tokens for specific period of time /// @dev Can be called only by owner function setLockingManager(address stakingToken, address lockingManager) external; /// @notice Starts staking for given token /// @param asset Token to start staking for /// @dev This function can be called only by owner /// @dev If this is the first time staking is started for given token, staked token will be deployed and added to the list function startStaking(address asset) external; /// @notice Configures emission per second for given reward token for specific staking token /// @param stakingToken Staking token to configure emission for /// @param rewardToken Reward token to configure emission for /// @param config Struct containing start and end timestamp and emission per second /// @dev This function can be called only by owner /// @dev If reward token is not in the list it will be added to the list otherwise config will be updated function configureRewardToken(address stakingToken, address rewardToken, RewardTokenConfig calldata config) external; /// @notice Stakes tokens from sender on behalf of given account, given account will staked tokens /// @param stakingToken Staking token to stake /// @param amount Amount to stake /// @param recipient Account to stake for function stake(address stakingToken, uint256 amount, address recipient) external; /// @notice Unstakes tokens from sender on behalf of given account, given account will receive tokens /// @param stakingToken Staking token to unstake /// @param amount Amount to unstake /// @param recipient Account to receive tokens function unstake(address stakingToken, uint256 amount, address recipient) external; /// @notice Unstakes tokens from sender on behalf of given account, given account will receive tokens and claim rewards /// @param stakingToken Staking token to unstake /// @param amount Amount to unstake /// @param recipient Account to receive tokens and rewards function unstakeAndClaim(address stakingToken, uint256 amount, address recipient) external; /// @notice Claims rewards for sender on given recipient account for given staking token /// @param stakingToken Staking token to claim rewards for /// @param recipient Account to receive rewards function claimRewards(address stakingToken, address recipient) external; /// @notice Claims rewards for sender on given recipient account for specific token /// @param stakingToken Staking token to claim rewards for /// @param rewardToken Token to claim rewards for /// @param recipient Account to receive rewards function claimRewardsForToken(address stakingToken, address rewardToken, address recipient) external; /// @notice Update hook for StakedToken smart contract /// @notice When user transfers, mints or burns staked tokens this hook is called and position is transfered to new owner /// @param stakingToken Staking token that is transfered /// @param sender Account that is transferring /// @param recipient Account that is receiving /// @param amount Amount that is transferred function updateHook(address stakingToken, address sender, address recipient, uint256 amount) external; }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; interface IStakingWithEpochs { enum RewardToken { USC, CHI, STETH } struct EpochData { uint256 shares; mapping(RewardToken => uint256) cumulativeRewardsPerShare; } struct StakeData { uint256 lastUpdatedEpoch; uint256 shares; uint256 addSharesNextEpoch; mapping(RewardToken => uint256) unclaimedRewards; } event Stake(address indexed account, uint256 amount); event Unstake(address indexed account, address indexed toAddress, uint256 amount); error ZeroAmount(); error NotRewardController(); error AmountBelowStakedBalance(uint256 stakedBalance, uint256 amount); /// @notice Gets current reward for given account and token /// @param account Account to get reward for /// @param token Token to get reward for /// @return amount Current reward for given account and token /// @custom:usage This function should be used in inheriting contracts to get current reward for given account and token function getUnclaimedRewards(address account, RewardToken token) external view returns (uint256 amount); /// @notice Gets cumulative reward per share for given account and token /// @param epoch Epoch to get cumulative reward per share for /// @param token Token to get cumulative reward per share for /// @return amount Cumulative reward per share for given epoch and token /// @custom:usage This function should be used in inheriting contracts to get cumulative reward per share for given epoch and token function getCumulativeRewardsPerShare(uint256 epoch, RewardToken token) external view returns (uint256 amount); /// @notice Stakes given amount of tokens /// @param amount Amount of tokens to stake /// @custom:usage This function should be called from inheriting contracts to stake tokens /// @custom:usage Logic should be the same for both uscStaking and chiStaking contracts function stake(uint256 amount) external; /// @notice Unstakes given amount of tokens, sends tokens to msg.sender by default /// @param amount Amount of tokens to unstake /// @custom:usage This function should be called from inheriting contracts to unstake tokens /// @custom:usage Logic should be the same for both uscStaking and chiStaking contracts function unstake(uint256 amount) external; /// @notice Unstakes given amount of tokens /// @param amount Amount of tokens to unstake /// @param toAddress Address to send tokens /// @custom:usage This function should be called from inheriting contracts to unstake tokens /// @custom:usage Logic should be the same for both uscStaking and chiStaking contracts function unstake(uint256 amount, address toAddress) external; }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; interface ISTETH is IERC20 { function submit(address referral) external payable returns (uint256); }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; interface ISTUSC { error InsufficientFunds(); event Stake(address indexed from, address indexed to, uint256 amount); event Unstake(address indexed from, address indexed to, uint256 amount); event SetEmissionPerSecond(uint256 emissionPerSecond); event SetStartTimestamp(uint256 startTimestamp); /// @notice Set USC token address /// @param usc Address of USC token /// @dev Only owner can call this function function setUsc(IERC20 usc) external; /// @notice Set emission per second /// @param _emissionPerSecond Emission per second /// @dev Only owner can call this function function setEmissionPerSecond(uint256 _emissionPerSecond) external; /// @notice Set start timestamp /// @param _startTimestamp Start timestamp /// @dev Only owner can call this function function setStartTimestamp(uint256 _startTimestamp) external; /// @notice Stake USC to get stUSC /// @param amount Amount of USC to stake /// @param recipient Receiver of stUSC function stake(uint256 amount, address recipient) external; /// @notice Unstake stUSC to get USC /// @param amount Amount of stUSC to unstake /// @param recipient Receiver of USC function unstake(uint256 amount, address recipient) external; }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; interface ITimeWeightedBonding { event SetCliffTimestampEnd(uint256 indexed cliffTimestampEnd); event RecoverChi(address indexed account, uint256 amount); event Buy(address indexed account, uint256 amount, uint256 ethCost); event Vest(address indexed account, uint256 amount); error EtherSendFailed(address to, uint256 value); /// @notice Sets timestamp when cliff period ends in ChiVesting contract /// @dev This can be replaced by getting cliff period from ChiVesting contract /// @dev It is kept this way so owner can dictate discount by changing this parameter /// @param _cliffTimestampEnd Timestamp when cliff period ends function setCliffTimestampEnd(uint256 _cliffTimestampEnd) external; /// @notice Vests given amount of CHI tokens for given user /// @param user Address of user to vest tokens for /// @param amount Amount of CHI tokens to vest /// @custom:usage This function should be called from owner in purpose of vesting CHI tokens for initial team function vest(address user, uint256 amount) external; /// @notice Buys CHI tokens for discounted price for caller, sends ETH to treasury /// @param amount Amount of CHI tokens to buy function buy(uint256 amount) external payable; /// @notice Recovers CHI tokens from contract /// @param amount Amount of CHI tokens to recover /// @custom:usage This function should be called from owner in purpose of recovering CHI tokens that are not sold function recoverChi(uint256 amount) external; }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {IMintable} from "./IMintable.sol"; interface IToken is IMintable, IERC20 { /// @notice Returns name of token /// @return name Name of token function name() external view returns (string memory name); /// @notice Returns symbol of token /// @return symbol Symbol of token function symbol() external view returns (string memory symbol); /// @notice Returns decimals of token /// @return decimals Decimals of token function decimals() external view returns (uint8 decimals); /// @notice Mints given amount of token to given account /// @param account Account to mint token to /// @param amount Amount of token to mint /// @custom:usage This function should be called from Arbitrage contract in purpose of minting token function mint(address account, uint256 amount) external; /// @notice Burns given amount from caller /// @param amount Amount of token to burn /// @custom:usage This function should be called from Arbitrage contract in purpose of burning token function burn(uint256 amount) external; }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; interface ITreasury { error EtherSendFailed(address to, uint256 amount); /// @notice transfers erc20 tokens or native tokens from the treasury vauler /// @param token address of the token to transfer, or address(0) for native token /// @param destination address to send tokens to /// @param amount amount of tokens to send function transfer(address token, address destination, uint256 amount) external; }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "./IOracle.sol"; interface IUniswapV2TwapOracle is IOracle { struct CumulativePriceSnapshot { uint256 price0; uint256 price1; uint32 blockTimestamp; } event UpdateCumulativePricesSnapshot(); error NoReserves(); error InvalidToken(); error PeriodNotPassed(); /// @notice Takes cumulative price snapshot and updates previous and last snapshots /// @notice Cumulative price is in quote token /// @custom:usage This function should be called periodically by external keeper function updateCumulativePricesSnapshot() external; /// @notice Gets TWAP quote for given token /// @notice TWAP quote is in quote token /// @param token Token address /// @param amountIn Amount of token to get quote for /// @return amountOut Quote amount in quote token function getTwapQuote(address token, uint256 amountIn) external view returns (uint256 amountOut); /// @notice Gets TWAP quote for base token /// @dev This function is used by PriceFeedAggregator contract /// @return price price in USD for the 1 baseToken function peek() external view returns (uint256 price); }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "./IToken.sol"; interface IUSC is IToken {}
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import {IStaking} from "./IStaking.sol"; import {IArbitrage} from "./IArbitrage.sol"; interface IUSCStaking is IStaking { event UpdateEpoch(uint256 indexed epoch, uint256 chiEmissions, uint256 uscRewards, uint256 stETHrewards); event LockChi(address indexed account, uint256 amount, uint256 duration); event ClaimUSCRewards(address indexed account, uint256 amount); event ClaimStETH(address indexed account, uint256 amount); error NotClaimable(); error InvalidDuration(uint256 duration); /// @notice Updates epoch data /// @param chiEmissions Amount of CHI token incentives emitted in current epoch for USC stakers /// @param uscRewards Amount of USC token frozen in current epoch for USC stakers /// @param stETHrewards Amount of stETH token rewards in current epoch for USC stakers /// @custom:usage This function should be called from rewardController contract in purpose of updating epoch data function updateEpoch(uint256 chiEmissions, uint256 uscRewards, uint256 stETHrewards) external; /// @notice Locks CHI tokens that user earned from incentives for given duration /// @param duration Locking duration in epochs function lockChi(uint256 duration) external; /// @notice Claims USC rewards for caller /// @dev This function can be called only when price is above target and there is excess of reserves function claimUSCRewards() external; }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; interface IweETH is IERC20 { /// @notice Wraps eEth /// @param _eETHAmount the amount of eEth to wrap /// @return returns the amount of weEth the user receives function wrap(uint256 _eETHAmount) external returns (uint256); }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; interface IWETH is IERC20 { function deposit() external payable; function withdraw(uint256 amount) external; }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; /// @notice external contract addresses on Ethereum Mainnet library ExternalContractAddresses { address public constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; address public constant stETH = 0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84; address public constant eETH = 0x35fA164735182de50811E8e2E824cFb9B6118ac2; address public constant weETH = 0xCd5fE23C85820F7B72D0926FC9b05b43E359b7ee; address public constant eETH_POOL = 0x308861A430be4cce5502d0A12724771Fc6DaF216; address public constant UNI_V2_SWAP_ROUTER = 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D; address public constant UNI_V3_SWAP_ROUTER = 0xE592427A0AEce92De3Edee1F18E0157C05861564; address public constant UNI_V2_POOL_FACTORY = 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f; address public constant ETH_USD_CHAINLINK_FEED = 0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419; address public constant STETH_USD_CHAINLINK_FEED = 0xCfE54B5cD566aB89272946F602D76Ea879CAb4a8; address public constant WEETH_ETH_CHAINLINK_FEED = 0x5c9C449BbC9a6075A2c061dF312a35fd1E05fF22; address public constant CURVE_ETH_STETH_POOL = 0xDC24316b9AE028F1497c275EB9192a3Ea0f67022; address public constant ONE_INCH_ROUTER = 0x111111125421cA6dc452d289314280a0f8842A65; }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "../interfaces/IWETH.sol"; import "../interfaces/ISTETH.sol"; import "contracts/library/ExternalContractAddresses.sol"; library LidoSubmitLibrary { using SafeERC20 for IERC20; IWETH public constant WETH = IWETH(ExternalContractAddresses.WETH); ISTETH public constant stEth = ISTETH(ExternalContractAddresses.stETH); function swapExactAmountForStEth(address assetIn, uint256 amountIn, uint256 minAmountOut, address receiver) external returns (uint256) { IERC20(WETH).safeTransferFrom(receiver, address(this), amountIn); WETH.withdraw(amountIn); // IERC20(assetIn).approve(address(stEth), amountIn); uint256 stEthReceived = stEth.submit{value: amountIn}(address(this)); IERC20(stEth).safeTransfer(receiver, stEthReceived); return stEthReceived; } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol"; import "contracts/library/ExternalContractAddresses.sol"; library UniswapV2SwapLibrary { IUniswapV2Router02 public constant swapRouter = IUniswapV2Router02(ExternalContractAddresses.UNI_V2_SWAP_ROUTER); function swapExactAmountIn( address assetIn, address assetOut, uint256 amount, uint256 minAmountOut, address receiver ) external returns (uint256) { address[] memory path = new address[](2); path[0] = assetIn; path[1] = assetOut; IERC20(assetIn).approve(address(swapRouter), amount); uint256[] memory amounts = swapRouter.swapExactTokensForTokens( amount, minAmountOut, path, receiver, block.timestamp ); return amounts[1]; } function swapExactAmountOut( address assetIn, address assetOut, uint256 amountOut, uint256 maxAmountIn, address receiver ) external returns (uint256) { address[] memory path = new address[](2); path[0] = assetIn; path[1] = assetOut; IERC20(assetIn).approve(address(swapRouter), maxAmountIn); uint256[] memory amounts = swapRouter.swapTokensForExactTokens( amountOut, maxAmountIn, path, receiver, block.timestamp ); return amounts[0]; } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; library UniswapV3SwapLibrary { using SafeERC20 for IERC20; ISwapRouter public constant swapRouter = ISwapRouter(0xE592427A0AEce92De3Edee1F18E0157C05861564); function swapExactAmountIn( address assetIn, address assetOut, uint256 amount, uint256 minAmountOut, address receiver ) internal returns (uint256) { IERC20(assetIn).approve(address(swapRouter), amount); return swapRouter.exactInputSingle( ISwapRouter.ExactInputSingleParams({ tokenIn: assetIn, tokenOut: assetOut, fee: 500, recipient: receiver, deadline: block.timestamp, amountIn: amount, amountOutMinimum: minAmountOut, sqrtPriceLimitX96: 0 }) ); } function swapExactAmountOut( address assetIn, address assetOut, uint256 amountOut, uint256 maxAmountIn, address receiver ) internal returns (uint256) { IERC20(assetIn).approve(address(swapRouter), maxAmountIn); return swapRouter.exactOutputSingle( ISwapRouter.ExactOutputSingleParams({ tokenIn: assetIn, tokenOut: assetOut, fee: 3000, recipient: receiver, deadline: block.timestamp, amountOut: amountOut, amountInMaximum: maxAmountIn, sqrtPriceLimitX96: 0 }) ); } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; contract ERC20Mock is ERC20 { constructor() ERC20("ERC20Mock", "E20M") {} function mint(address account, uint256 amount) external { _mint(account, amount); } function burn(address account, uint256 amount) external { _burn(account, amount); } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; contract MockERC20 is ERC20 { constructor(string memory _name, string memory _symbol, uint256 initialSupply) ERC20(_name, _symbol) { _mint(msg.sender, initialSupply); } function burnFrom(address account, uint256 amount) public { _burn(account, amount); } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import {IOracle} from "../interfaces/IOracle.sol"; contract MockOracle is IOracle { uint256 public price; function name() external pure override returns (string memory) { return "MockOracle"; } function decimals() external pure override returns (uint8) { return 8; } function setPrice(uint256 _price) external { price = _price; } function peek() external view override returns (uint256) { return price; } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/access/Ownable.sol"; import "../interfaces/IPriceFeedAggregator.sol"; import "../interfaces/IOracle.sol"; contract MockPriceFeedAggregator is IPriceFeedAggregator, Ownable { mapping(address => IOracle) public priceFeeds; function setPriceFeed(address base, address feed) external onlyOwner { if (base == address(0) || feed == address(0)) { revert ZeroAddress(); } priceFeeds[base] = IOracle(feed); } mapping(address => uint256) public mockPrice; function setMockPrice(address token, uint256 price) external { mockPrice[token] = price; } function peek(address base) external view override returns (uint256 price) { return mockPrice[base]; } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import "@openzeppelin/contracts/utils/math/Math.sol"; import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; import "../library/ExternalContractAddresses.sol"; import "../interfaces/IChainlinkEthAdapter.sol"; /// @title Oracle adapter for Chainlink price feeds /// @notice PriceFeedAggregator contract uses this contract to get price for specific token /// @notice One instance of this contract handles one Chainlink price feed contract ChainlinkEthAdapter is IChainlinkEthAdapter { uint128 public constant BASE_AMOUNT = 1e18; // Both WETH and stETH will have 18 decimals uint8 public immutable decimals; address public immutable baseToken; AggregatorV3Interface public immutable chainlinkFeed; constructor(address _baseToken, address _chainlinkFeed) { baseToken = _baseToken; chainlinkFeed = AggregatorV3Interface(_chainlinkFeed); decimals = chainlinkFeed.decimals(); } /// @inheritdoc IOracle function name() external view returns (string memory) { return string(abi.encodePacked("Chainlink Price - ", IERC20Metadata(baseToken).symbol())); } /// @inheritdoc IOracle function peek() external view returns (uint256 price) { (, int256 priceInEth, , , ) = chainlinkFeed.latestRoundData(); assert(priceInEth > 0); // prices for assets from chainlink should always be grater than 0 (, int256 ethPriceInUsd, , , ) = AggregatorV3Interface(ExternalContractAddresses.ETH_USD_CHAINLINK_FEED) .latestRoundData(); assert(priceInEth > 0); // prices for assets from chainlink should always be grater than 0 uint256 priceInUsd = Math.mulDiv(uint256(priceInEth), uint256(ethPriceInUsd), 10 ** chainlinkFeed.decimals()); return priceInUsd; } /// @inheritdoc IChainlinkEthAdapter function exchangeRate() external view returns (uint256) { (, int256 priceInEth, , , ) = chainlinkFeed.latestRoundData(); assert(priceInEth > 0); // prices for assets from chainlink should always be grater than 0 return uint256(priceInEth); } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; import "../interfaces/IOracle.sol"; /// @title Oracle adapter for Chainlink price feeds /// @notice PriceFeedAggregator contract uses this contract to get price for specific token /// @notice One instance of this contract handles one Chainlink price feed contract ChainlinkOracle is IOracle { uint128 public constant BASE_AMOUNT = 1e18; // Both WETH and stETH will have 18 decimals uint8 public immutable decimals; address public immutable baseToken; AggregatorV3Interface public immutable chainlinkFeed; constructor(address _baseToken, address _chainlinkFeed) { baseToken = _baseToken; chainlinkFeed = AggregatorV3Interface(_chainlinkFeed); decimals = chainlinkFeed.decimals(); } /// @inheritdoc IOracle function name() external view returns (string memory) { return string(abi.encodePacked("Chainlink Price - ", IERC20Metadata(baseToken).symbol())); } /// @inheritdoc IOracle function peek() external view returns (uint256 price) { (, int256 priceInUSD, , , ) = chainlinkFeed.latestRoundData(); assert(priceInUSD > 0); // prices for assets from chainlink should always be grater than 0 return uint256(priceInUSD); } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/access/Ownable.sol"; import "../interfaces/IPriceFeedAggregator.sol"; import "../interfaces/IOracle.sol"; /// @title Contract for handling different price feeds /// @notice Protocol uses this contract to get price feeds for different tokens /// @notice Owner of contract can set price feed adapters for different tokens contract PriceFeedAggregator is IPriceFeedAggregator, Ownable { mapping(address asset => IOracle oracle) public priceFeeds; /// @inheritdoc IPriceFeedAggregator function setPriceFeed(address base, address feed) external onlyOwner { if (base == address(0) || feed == address(0)) { revert ZeroAddress(); } priceFeeds[base] = IOracle(feed); emit SetPriceFeed(base, feed); } /// @inheritdoc IPriceFeedAggregator function peek(address base) external view returns (uint256 price) { if (address(priceFeeds[base]) == address(0)) { revert ZeroAddress(); } return (priceFeeds[base].peek()); } }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.0; import "@uniswap/v2-core/contracts/interfaces/IUniswapV2Factory.sol"; import "@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol"; import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import "contracts/interfaces/IUniswapV2TwapOracle.sol"; import "contracts/uniswap/libraries/FixedPoint.sol"; import "contracts/uniswap/libraries/UniswapV2OracleLibrary.sol"; import "contracts/uniswap/libraries/UniswapV2Library.sol"; /// @title Uniswap V2 TWAP Oracle /// @notice PriceFeedAggregator uses this contract to be TWAP price of USC and CHI tokens /// @notice One instance of this contract handles one Uniswap V2 pair /// @notice This contract takes snapshots of cumulative prices and calculates TWAP price from them contract UniswapV2TwapOracle is IUniswapV2TwapOracle { using FixedPoint for *; AggregatorV3Interface public immutable quoteTokenChainlinkFeed; IUniswapV2Pair public immutable pair; uint8 public immutable decimals; uint32 public immutable updatePeriod; uint32 public immutable minPeriodFromLastSnapshot; address public immutable baseToken; uint128 public immutable baseAmount; // should be 10^baseToken.decimals address public immutable token0; address public immutable token1; CumulativePriceSnapshot public previousSnapshot; CumulativePriceSnapshot public lastSnapshot; constructor( address _factory, address _baseToken, address _quoteToken, uint32 _updatePeriod, uint32 _minPeriodFromLastSnapshot, AggregatorV3Interface _quoteTokenChainlinkFeed ) { baseToken = _baseToken; baseAmount = uint128(10 ** (IERC20Metadata(baseToken).decimals())); updatePeriod = _updatePeriod; minPeriodFromLastSnapshot = _minPeriodFromLastSnapshot; quoteTokenChainlinkFeed = _quoteTokenChainlinkFeed; decimals = _quoteTokenChainlinkFeed.decimals(); IUniswapV2Pair _pair = IUniswapV2Pair(UniswapV2Library.pairFor(_factory, _baseToken, _quoteToken)); pair = _pair; token0 = _pair.token0(); token1 = _pair.token1(); (uint256 price0Cumulative, uint256 price1Cumulative, uint32 blockTimestamp) = UniswapV2OracleLibrary .currentCumulativePrices(address(pair)); lastSnapshot = CumulativePriceSnapshot({ price0: price0Cumulative, price1: price1Cumulative, blockTimestamp: blockTimestamp }); previousSnapshot = lastSnapshot; uint112 reserve0; uint112 reserve1; (reserve0, reserve1, lastSnapshot.blockTimestamp) = _pair.getReserves(); // ensure that there's liquidity in the pair if (reserve0 == 0 || reserve1 == 0) { revert NoReserves(); } } /// @inheritdoc IOracle function name() external view returns (string memory) { return string(abi.encodePacked("UniV2 TWAP - ", IERC20Metadata(baseToken).symbol())); } /// @inheritdoc IUniswapV2TwapOracle function updateCumulativePricesSnapshot() public { (uint256 price0Cumulative, uint256 price1Cumulative, uint32 blockTimestamp) = UniswapV2OracleLibrary .currentCumulativePrices(address(pair)); if (blockTimestamp - lastSnapshot.blockTimestamp < updatePeriod) { revert PeriodNotPassed(); } previousSnapshot = lastSnapshot; lastSnapshot = CumulativePriceSnapshot({ price0: price0Cumulative, price1: price1Cumulative, blockTimestamp: blockTimestamp }); emit UpdateCumulativePricesSnapshot(); } /// @inheritdoc IUniswapV2TwapOracle function getTwapQuote(address token, uint256 amountIn) public view returns (uint256 amountOut) { (uint256 price0Cumulative, uint256 price1Cumulative, uint32 blockTimestamp) = UniswapV2OracleLibrary .currentCumulativePrices(address(pair)); uint32 timeElapsedFromLast = blockTimestamp - lastSnapshot.blockTimestamp; CumulativePriceSnapshot storage snapshot; if (timeElapsedFromLast >= minPeriodFromLastSnapshot) { snapshot = lastSnapshot; } else { snapshot = previousSnapshot; } uint32 timeElapsed = blockTimestamp - snapshot.blockTimestamp; if (token == token0) { // overflow is desired, casting never truncates // cumulative price is in (uq112x112 price * seconds) units so we simply wrap it after division by time elapsed FixedPoint.uq112x112 memory price0Average = FixedPoint.uq112x112( uint224((price0Cumulative - snapshot.price0) / timeElapsed) ); amountOut = price0Average.mul(amountIn).decode144(); } else { if (token != token1) { revert InvalidToken(); } FixedPoint.uq112x112 memory price1Average = FixedPoint.uq112x112( uint224((price1Cumulative - snapshot.price1) / timeElapsed) ); amountOut = price1Average.mul(amountIn).decode144(); } return amountOut; } /// @inheritdoc IUniswapV2TwapOracle function peek() external view returns (uint256 price) { uint256 quotedAmount = getTwapQuote(baseToken, baseAmount); (, int256 quoteTokenPriceInUSD, , , ) = quoteTokenChainlinkFeed.latestRoundData(); uint256 priceInUSD = (uint256(quoteTokenPriceInUSD) * quotedAmount) / baseAmount; // quote token price will be always greater than 0, so it's ok to convert int to uint return priceInUSD; } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import {ReserveHolderV2} from "../reserve/ReserveHolderV2.sol"; import {PriceFeedAggregator} from "../oracles/PriceFeedAggregator.sol"; import {IAdapter} from "../interfaces/IAdapter.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; contract ReserveHolderDataProvider { address public constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; struct ReserveData { address reserveAsset; uint256 reserveAssetPrice; uint256 reserveValue; uint256 rewardValue; } struct ReserveBreakdown { uint256 totalValue; ReserveData[] reserves; } function getReserveBreakdown( ReserveHolderV2 reserveHolder, PriceFeedAggregator priceFeedAggregator ) external view returns (ReserveBreakdown memory breakdown) { address[] memory reserveAssets = reserveHolder.getReserveAssets(); ReserveData[] memory reserves = new ReserveData[](reserveAssets.length + 1); for (uint256 i = 0; i < reserveAssets.length; i++) { address reserveAsset = reserveAssets[i]; uint256 reserveAssetPrice = priceFeedAggregator.peek(reserveAsset); address adapter = address(reserveHolder.reserveAdapters(reserveAsset)); uint256 reserveValue = IAdapter(adapter).getReserveValue(); uint256 balance = IERC20(reserveAsset).balanceOf(adapter); uint256 balanceUsd = (balance * reserveAssetPrice) / 1e18; uint256 generatedYieldUsd = balanceUsd - reserveValue; reserves[i] = ReserveData({ reserveAsset: reserveAsset, reserveAssetPrice: reserveAssetPrice, reserveValue: reserveValue, rewardValue: generatedYieldUsd }); } uint256 wethValue = (IERC20(WETH).balanceOf(address(reserveHolder)) * priceFeedAggregator.peek(WETH)) / 1e18; reserves[reserves.length - 1] = ReserveData({ reserveAsset: WETH, reserveAssetPrice: priceFeedAggregator.peek(WETH), reserveValue: wethValue, rewardValue: 0 }); uint256 totalValue = reserveHolder.getReserveValue(); return ReserveBreakdown({totalValue: totalValue, reserves: reserves}); } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol"; import "../library/ExternalContractAddresses.sol"; import "../interfaces/ISTETH.sol"; /// @title Testpage Helper /// @notice Helper for the dashboard used to test protocol on the testnet contract TestpageHelper { error EthValueIsZero(); address public constant WETH = ExternalContractAddresses.WETH; ISTETH public constant stETH = ISTETH(ExternalContractAddresses.stETH); IUniswapV2Router02 public constant swapRouter = IUniswapV2Router02(ExternalContractAddresses.UNI_V2_SWAP_ROUTER); function addStETHRewards(address reserveHolder) external payable { if (msg.value == 0) revert EthValueIsZero(); stETH.submit{value: msg.value}(address(this)); IERC20(stETH).transfer(reserveHolder, msg.value); } function _makePath(address t1, address t2) internal pure returns (address[] memory path) { path = new address[](2); path[0] = t1; path[1] = t2; } function _makePath(address t1, address t2, address t3) internal pure returns (address[] memory path) { path = new address[](3); path[0] = t1; path[1] = t2; path[2] = t3; } function exchange(address tokenIn, address tokenOut, uint256 amount) public returns (uint256) { IERC20(tokenIn).transferFrom(msg.sender, address(this), amount); uint256 amountOut = _exchange(tokenIn, tokenOut, amount); IERC20(tokenOut).transfer(msg.sender, amountOut); return amountOut; } function exchangeManyTimes( address token0, address token1, uint256 startAmount, uint256 times ) external returns (uint256) { IERC20(token0).transferFrom(msg.sender, address(this), startAmount); uint256 amount = startAmount; for (uint256 i = 0; i < times; i++) { if (i % 2 == 0) { amount = _exchange(token0, token1, amount); } else { amount = _exchange(token1, token0, amount); } } IERC20(times % 2 == 0 ? token0 : token1).transfer(msg.sender, amount); return amount; } function _exchange(address tokenIn, address tokenOut, uint256 amount) public returns (uint256) { address[] memory path; if (tokenIn != WETH && tokenOut != WETH) { path = _makePath(tokenIn, WETH, tokenOut); } else { path = _makePath(tokenIn, tokenOut); } IERC20(tokenIn).approve(address(swapRouter), amount); uint256[] memory amounts = swapRouter.swapExactTokensForTokens(amount, 0, path, address(this), block.timestamp); uint256 amountReceived = amounts[path.length - 1]; return amountReceived; } function addLiquidity(address tokenA, address tokenB, uint256 amountA, uint256 amountB) external { IERC20(tokenA).transferFrom(msg.sender, address(this), amountA); IERC20(tokenB).transferFrom(msg.sender, address(this), amountB); IERC20(tokenA).approve(address(swapRouter), amountA); IERC20(tokenB).approve(address(swapRouter), amountB); swapRouter.addLiquidity(tokenA, tokenB, amountA, amountB, 0, 0, msg.sender, block.timestamp); } function addLiquidityETH(address token, uint256 amountToken) external payable { IERC20(token).transferFrom(msg.sender, address(this), amountToken); IERC20(token).approve(address(swapRouter), amountToken); swapRouter.addLiquidityETH{value: msg.value}(token, amountToken, 0, 0, msg.sender, block.timestamp); } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/utils/math/Math.sol"; import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; import "../interfaces/IAdapter.sol"; import "../interfaces/IChainlinkEthAdapter.sol"; import "../interfaces/IPriceFeedAggregator.sol"; import "../library/ExternalContractAddresses.sol"; import "../library/UniswapV2SwapLibrary.sol"; import "../library/UniswapV3SwapLibrary.sol"; import "forge-std/console.sol"; abstract contract Adapter is IAdapter, Ownable { using SafeERC20 for IERC20; IPriceFeedAggregator public immutable priceFeedAggregator; address public immutable reserveHolder; address public immutable asset; Pool public poolType; uint256 public totalDeposited; modifier onlyReserveHolder() { if (msg.sender != reserveHolder) { revert NotReserveHolder(); } _; } constructor(address _reserveHolder, address _priceFeedAggregator, address _asset) Ownable() { reserveHolder = _reserveHolder; priceFeedAggregator = IPriceFeedAggregator(_priceFeedAggregator); asset = _asset; } /// @inheritdoc IAdapter function setPoolType(Pool _poolType) external onlyOwner { poolType = _poolType; emit SetPoolType(_poolType); } /// @inheritdoc IAdapter function rescueReserves() external onlyOwner { IERC20(asset).safeTransfer(msg.sender, IERC20(asset).balanceOf(address(this))); totalDeposited = 0; emit RescueReserves(); } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/utils/math/Math.sol"; import "@openzeppelin/contracts/utils/math/Math.sol"; import "../interfaces/IPriceFeedAggregator.sol"; import "../interfaces/ISTETH.sol"; import "../interfaces/IWETH.sol"; import "../interfaces/ICurvePool.sol"; import "../interfaces/IReserveHolderV2.sol"; import "../interfaces/IChainlinkEthAdapter.sol"; import "../interfaces/IAdapter.sol"; import "../library/ExternalContractAddresses.sol"; /// @title Contract for holding LST reserves /// @notice This contract holds LST reserves and rebalances them /// @notice Part of reserves are is WETH so arbitrage can take them and perform aribtrage without swapping LSTs for WETH /// @dev This contract is upgradeable contract ReserveHolderV2 is IReserveHolderV2, OwnableUpgradeable { using SafeERC20 for ISTETH; using SafeERC20 for IWETH; using SafeERC20 for IERC20; uint256 public constant BASE_PRICE = 1e8; uint256 public constant MAX_PERCENTAGE = 100_00; IWETH public constant WETH = IWETH(ExternalContractAddresses.WETH); ISTETH public constant stETH = ISTETH(ExternalContractAddresses.stETH); ICurvePool public constant curvePool = ICurvePool(ExternalContractAddresses.CURVE_ETH_STETH_POOL); IPriceFeedAggregator public priceFeedAggregator; address public claimer; uint256 public swapTolerance; address[] public reserveAssets; mapping(address account => bool status) public isArbitrager; mapping(address => IAdapter) public reserveAdapters; mapping(address => uint256) public percentages; mapping(address => bool) public isRebalancer; modifier onlyArbitrager() { if (isArbitrager[msg.sender] != true) { revert NotArbitrager(); } _; } modifier onlyClaimer() { if (msg.sender != claimer) { revert NotClaimer(); } _; } modifier onlyRebalancer() { if (isRebalancer[msg.sender] != true) { revert NotRebalancer(); } _; } function initialize(IPriceFeedAggregator _priceFeedAggregator) external initializer { __Ownable_init(); priceFeedAggregator = _priceFeedAggregator; } /// @inheritdoc IReserveHolderV2 function getReserveAssets() external view returns (address[] memory) { return reserveAssets; } /// @inheritdoc IReserveHolderV2 function setArbitrager(address arbitrager, bool status) external onlyOwner { isArbitrager[arbitrager] = status; emit SetArbitrager(arbitrager, status); } /// @inheritdoc IReserveHolderV2 function setClaimer(address _claimer) external onlyOwner { claimer = _claimer; emit SetClaimer(_claimer); } /// @inheritdoc IReserveHolderV2 function setRebalancer(address rebalancer, bool status) external onlyOwner { isRebalancer[rebalancer] = status; emit SetRebalancer(rebalancer, status); } /// @inheritdoc IReserveHolderV2 function setSwapTolerance(uint256 _swapTolerance) external onlyOwner { if (_swapTolerance > MAX_PERCENTAGE) { revert PercentageTooHigh(); } swapTolerance = _swapTolerance; emit SetSwapTolerance(_swapTolerance); } /// @inheritdoc IReserveHolderV2 function addReserveAsset(address reserveAsset, address reserveAdapter, uint256 reservePercentage) external onlyOwner { for (uint256 i = 0; i < reserveAssets.length; i++) { if (reserveAssets[i] == reserveAsset) { revert AssetAlreadyAdded(); } } reserveAssets.push(reserveAsset); setReserveAssetAdapter(reserveAsset, reserveAdapter); setReserveAssetPercentage(reserveAsset, reservePercentage); emit AddReserveAsset(reserveAsset); } /// @inheritdoc IReserveHolderV2 function setReserveAssetAdapter(address reserveAsset, address adapter) public onlyOwner { address oldAdapter = address(reserveAdapters[reserveAsset]); if (oldAdapter != address(0)) { IERC20(reserveAsset).approve(oldAdapter, 0); IERC20(WETH).approve(oldAdapter, 0); } reserveAdapters[reserveAsset] = IAdapter(adapter); IERC20(reserveAsset).approve(adapter, type(uint256).max); IERC20(WETH).approve(adapter, type(uint256).max); emit SetReserveAssetAdapter(reserveAsset, adapter); } /// @inheritdoc IReserveHolderV2 function setReserveAssetPercentage(address reserveAsset, uint256 percentage) public onlyOwner { if (percentage > MAX_PERCENTAGE) { revert PercentageTooHigh(); } percentages[reserveAsset] = percentage; emit SetReserveAssetPercentage(reserveAsset, percentage); } /// @inheritdoc IReserveHolderV2 function getReserveValue() public view returns (uint256) { uint256 wethBalance = WETH.balanceOf(address(this)); uint256 wethPrice = priceFeedAggregator.peek(ExternalContractAddresses.WETH); uint256 totalReserveValue = Math.mulDiv(wethBalance, wethPrice, 1e18); for (uint256 i = 0; i < reserveAssets.length; i++) { totalReserveValue += reserveAdapters[reserveAssets[i]].getReserveValue(); } return totalReserveValue; } /// @inheritdoc IReserveHolderV2 function deposit(address reserveAsset, uint256 amount) external { IERC20(reserveAsset).safeTransferFrom(msg.sender, address(this), amount); // If address of reserve adapter is 0x0 this means that either token is not supported or it is WETH and we just take it // WETH will be calculated in reserves even if it is not in reserveAssets if (reserveAsset != address(WETH)) { IAdapter reserveAdapter = reserveAdapters[reserveAsset]; reserveAdapter.deposit(IERC20(reserveAsset).balanceOf(address(this))); } emit Deposit(msg.sender, reserveAsset, amount); } /// @inheritdoc IReserveHolderV2 function rebalance(uint256[] calldata protectionParams) external onlyRebalancer { uint256 totalReserveValue = getReserveValue(); // First perform all sells and then buys to make sure we have enough WETH for buys for (uint256 i = 0; i < reserveAssets.length; i++) { uint256 protectionParam = protectionParams[i]; address reserveAsset = reserveAssets[i]; uint256 reserveAssetPercentage = percentages[reserveAsset]; uint256 reserveAssetTargetValue = Math.mulDiv(totalReserveValue, reserveAssetPercentage, MAX_PERCENTAGE); uint256 reserveAssetValue = reserveAdapters[reserveAsset].getReserveValue(); uint256 minReserveAssetValueForSell = Math.mulDiv( reserveAssetTargetValue, MAX_PERCENTAGE + swapTolerance, MAX_PERCENTAGE ); if (reserveAssetValue > minReserveAssetValueForSell) { uint256 reserveAssetValueToSwap = reserveAssetValue - reserveAssetTargetValue; uint256 reserveAssetAmountToSwap = Math.mulDiv( reserveAssetValueToSwap, 10 ** IERC20Metadata(reserveAsset).decimals(), _peek(reserveAsset) ); reserveAdapters[reserveAsset].swapAmountToEth(reserveAssetAmountToSwap, protectionParam, address(this)); } } for (uint256 i = 0; i < reserveAssets.length; i++) { address reserveAsset = reserveAssets[i]; uint256 reserveAssetPercentage = percentages[reserveAsset]; uint256 reserveAssetTargetValue = Math.mulDiv(totalReserveValue, reserveAssetPercentage, MAX_PERCENTAGE); uint256 reserveAssetValue = reserveAdapters[reserveAsset].getReserveValue(); uint256 maxReserveAssetValueForBuy = Math.mulDiv( reserveAssetTargetValue, MAX_PERCENTAGE - swapTolerance, MAX_PERCENTAGE ); if (reserveAssetValue < maxReserveAssetValueForBuy) { uint256 reserveAssetValueToBuy = reserveAssetTargetValue - reserveAssetValue; uint256 ethPrice = priceFeedAggregator.peek(ExternalContractAddresses.WETH); uint256 ethAmountToSell = Math.mulDiv(reserveAssetValueToBuy, 1e18, ethPrice); IERC20(WETH).approve(address(reserveAdapters[reserveAsset]), ethAmountToSell); reserveAdapters[reserveAsset].swapAmountFromEth(ethAmountToSell); } } emit Rebalance(); } /// @inheritdoc IReserveHolderV2 function redeem(uint256 amount, address reserve) external onlyArbitrager returns (uint256) { if (reserve == address(WETH)) { WETH.safeTransfer(msg.sender, amount); } else { reserveAdapters[reserve].withdraw(amount, msg.sender); } emit Redeem(msg.sender, reserve, amount); return amount; } /// @inheritdoc IReserveHolderV2 function claimRewards(address account) external onlyClaimer { for (uint256 i = 0; i < reserveAssets.length; i++) { reserveAdapters[reserveAssets[i]].claimRewards(account); } emit ClaimRewards(account); } /// @inheritdoc IReserveHolderV2 function swapReserveForEth(address reserve, uint256 amountIn, uint256 minAmountOut) external onlyArbitrager { reserveAdapters[reserve].swapAmountToEth(amountIn, minAmountOut, address(this)); } function _peek(address asset) private view returns (uint256) { uint256 price = priceFeedAggregator.peek(asset); return price; } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/utils/math/Math.sol"; import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "../interfaces/IAdapter.sol"; import "../interfaces/IChainlinkEthAdapter.sol"; import "../interfaces/IPriceFeedAggregator.sol"; import "../interfaces/ICurvePool.sol"; import "../interfaces/IWETH.sol"; import "../interfaces/ISTETH.sol"; import "./Adapter.sol"; contract StEthAdapter is Adapter { using SafeERC20 for IERC20; ICurvePool public constant curvePool = ICurvePool(ExternalContractAddresses.CURVE_ETH_STETH_POOL); IWETH public constant WETH = IWETH(ExternalContractAddresses.WETH); constructor( address _reserveHolder, address _priceFeedAggregator, address _asset ) Adapter(_reserveHolder, _priceFeedAggregator, _asset) {} /// @inheritdoc IAdapter function getReserveValue() external view override returns (uint256) { uint256 assetPrice = priceFeedAggregator.peek(asset); return Math.mulDiv(totalDeposited, assetPrice, 10 ** IERC20Metadata(asset).decimals()); } /// @inheritdoc IAdapter function deposit(uint256 amount) external { uint256 balanceBefore = IERC20(asset).balanceOf(address(this)); IERC20(asset).safeTransferFrom(msg.sender, address(this), amount); uint256 balanceAfter = IERC20(asset).balanceOf(address(this)); amount = balanceAfter - balanceBefore; totalDeposited += amount; emit Deposit(amount); } /// @inheritdoc IAdapter function withdraw(uint256 amount, address recipient) external onlyReserveHolder { IERC20(asset).safeTransfer(recipient, amount); totalDeposited -= amount; emit Withdraw(amount, recipient); } /// @inheritdoc IAdapter function claimRewards(address receiver) external onlyReserveHolder returns (uint256) { uint256 balance = IERC20(asset).balanceOf(address(this)); uint256 reward = balance - totalDeposited; IERC20(asset).safeTransfer(receiver, reward); emit ClaimRewards(receiver, reward); return reward; } /// @inheritdoc IAdapter function swapAmountToEth( uint256 amountIn, uint256 minAmountOut, address receiver ) external override onlyReserveHolder returns (uint256) { IERC20(asset).approve(address(curvePool), amountIn); uint256 ethReceived = curvePool.exchange(1, 0, amountIn, minAmountOut); totalDeposited -= amountIn; WETH.deposit{value: ethReceived}(); IERC20(WETH).safeTransfer(receiver, ethReceived); return ethReceived; } /// @inheritdoc IAdapter function swapAmountFromEth(uint256 amountIn) external onlyReserveHolder returns (uint256) { IERC20(WETH).safeTransferFrom(msg.sender, address(this), amountIn); IWETH(WETH).withdraw(amountIn); uint256 balanceBefore = IERC20(asset).balanceOf(address(this)); ISTETH(asset).submit{value: amountIn}(address(this)); uint256 stEthReceived = IERC20(asset).balanceOf(address(this)) - balanceBefore; totalDeposited += stEthReceived; return stEthReceived; } receive() external payable {} }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/utils/math/Math.sol"; import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "../interfaces/IWETH.sol"; import "../interfaces/IweETH.sol"; import "../interfaces/IEtherFiLiquidityPool.sol"; import "../interfaces/IAdapter.sol"; import "../interfaces/IChainlinkEthAdapter.sol"; import "../interfaces/IPriceFeedAggregator.sol"; import "../library/ExternalContractAddresses.sol"; import "../library/UniswapV3SwapLibrary.sol"; import "./Adapter.sol"; contract WeEthAdapter is Adapter { using SafeERC20 for IERC20; IWETH public constant WETH = IWETH(ExternalContractAddresses.WETH); IERC20 public constant eETH = IERC20(ExternalContractAddresses.eETH); constructor( address _reserveHolder, address _priceFeedAggregator, address _asset ) Adapter(_reserveHolder, _priceFeedAggregator, _asset) {} /// @inheritdoc IAdapter function getReserveValue() external view override returns (uint256) { uint256 assetPrice = priceFeedAggregator.peek(ExternalContractAddresses.WETH); return Math.mulDiv(totalDeposited, assetPrice, 1e18); } /// @inheritdoc IAdapter function deposit(uint256 amount) external { IERC20(asset).safeTransferFrom(msg.sender, address(this), amount); IChainlinkEthAdapter chainlinkEthAdapter = IChainlinkEthAdapter(address(priceFeedAggregator.priceFeeds(asset))); uint256 assetEthExchangeRate = chainlinkEthAdapter.exchangeRate(); uint256 amountInEth = Math.mulDiv(amount, assetEthExchangeRate, 10 ** IERC20Metadata(asset).decimals()); totalDeposited += amountInEth; emit Deposit(amount); } /// @inheritdoc IAdapter function withdraw(uint256 amount, address recipient) external onlyReserveHolder { IChainlinkEthAdapter chainlinkEthAdapter = IChainlinkEthAdapter(address(priceFeedAggregator.priceFeeds(asset))); uint256 assetEthExchangeRate = chainlinkEthAdapter.exchangeRate(); uint256 amountInEth = Math.mulDiv(amount, assetEthExchangeRate, 10 ** IERC20Metadata(asset).decimals()); totalDeposited -= amountInEth; IERC20(asset).safeTransfer(recipient, amount); emit Withdraw(amount, recipient); } /// @inheritdoc IAdapter function claimRewards(address receiver) external onlyReserveHolder returns (uint256) { IChainlinkEthAdapter chainlinkEthAdapter = IChainlinkEthAdapter(address(priceFeedAggregator.priceFeeds(asset))); uint256 assetEthExchangeRate = chainlinkEthAdapter.exchangeRate(); uint256 totalBalanceInEthScaled = IERC20(asset).balanceOf(address(this)) * assetEthExchangeRate; uint256 totalRewardsInEthScaled = totalBalanceInEthScaled - totalDeposited * 1e18; uint256 totalRewardsInAsset = Math.mulDiv(totalRewardsInEthScaled, 1, assetEthExchangeRate); IERC20(asset).safeTransfer(receiver, totalRewardsInAsset); emit ClaimRewards(receiver, totalRewardsInAsset); return totalRewardsInAsset; } /// @inheritdoc IAdapter function swapAmountToEth( uint256 amountIn, uint256 minAmountOut, address receiver ) external override onlyReserveHolder returns (uint256) { uint256 amountOut = UniswapV3SwapLibrary.swapExactAmountIn( asset, ExternalContractAddresses.WETH, amountIn, minAmountOut, receiver ); IChainlinkEthAdapter chainlinkEthAdapter = IChainlinkEthAdapter(address(priceFeedAggregator.priceFeeds(asset))); uint256 assetEthExchangeRate = chainlinkEthAdapter.exchangeRate(); totalDeposited -= Math.mulDiv(amountIn, assetEthExchangeRate, 10 ** IERC20Metadata(asset).decimals()); return amountOut; } /// @inheritdoc IAdapter function swapAmountFromEth(uint256 amountIn) external override onlyReserveHolder returns (uint256) { IERC20(WETH).safeTransferFrom(msg.sender, address(this), amountIn); IWETH(WETH).withdraw(amountIn); IEtherFiLiquidityPool(ExternalContractAddresses.eETH_POOL).deposit{value: amountIn}(); uint256 eEthReceived = IERC20(eETH).balanceOf(address(this)); IERC20(eETH).approve(asset, eEthReceived); uint256 weEthReceived = IweETH(asset).wrap(eEthReceived); IChainlinkEthAdapter chainlinkEthAdapter = IChainlinkEthAdapter(address(priceFeedAggregator.priceFeeds(asset))); uint256 assetEthExchangeRate = chainlinkEthAdapter.exchangeRate(); totalDeposited += Math.mulDiv(weEthReceived, assetEthExchangeRate, 10 ** IERC20Metadata(asset).decimals()); return weEthReceived; } receive() external payable {} }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/utils/math/Math.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; import "../interfaces/IIDO.sol"; import "../interfaces/ICHI.sol"; import "../interfaces/IChiVesting.sol"; /// @title IDO /// @notice Contract handles IDO for CHI tokens contract IDO is IIDO, Ownable, ReentrancyGuard { ICHI public chi; IChiVesting public chiVesting; uint256 public startTimestamp; uint256 public endTimestamp; uint256 public minValue; uint256 public maxValue; uint256 public softCap; uint256 public hardCap; uint256 public totalSale; uint256 public price; address public treasury; bool public claimingEnabled; mapping(address account => uint256 amount) public ethAmount; mapping(address account => bool claimed) public claimed; mapping(address account => uint256 amount) public boughtChiAmount; mapping(address account => bool whitelisted) public whitelisted; uint256 public constant MAX_TAX_PERCENT = 100_00000000; uint256 public startTaxPercent; uint256 public taxPercentFallPerSec; constructor( ICHI _chi, IChiVesting _chiVesting, uint256 _startTimestamp, uint256 _endTimestamp, uint256 _minValue, uint256 _maxValue, uint256 _softCap, uint256 _hardCap, uint256 _price, address _treasury, uint256 _startTaxPercent, uint256 _taxPercentFallPerSec ) Ownable() { chi = _chi; chiVesting = _chiVesting; startTimestamp = _startTimestamp; endTimestamp = _endTimestamp; minValue = _minValue; maxValue = _maxValue; softCap = _softCap; hardCap = _hardCap; price = _price; treasury = _treasury; startTaxPercent = _startTaxPercent; taxPercentFallPerSec = _taxPercentFallPerSec; totalSale = 0; claimingEnabled = false; } function changeTime(uint256 _startTimestamp, uint256 _endTimestamp) external onlyOwner { startTimestamp = _startTimestamp; endTimestamp = _endTimestamp; } function changeValueRange(uint256 _minValue, uint256 _maxValue) external onlyOwner { minValue = _minValue; maxValue = _maxValue; } function changeTax(uint256 _startTaxPercent, uint256 _taxPercentFallPerSec) external onlyOwner { startTaxPercent = _startTaxPercent; taxPercentFallPerSec = _taxPercentFallPerSec; } function changeClaimingEnabled(bool _claimingEnabled) external onlyOwner { claimingEnabled = _claimingEnabled; } function changePrice(uint256 _price) external onlyOwner { if (block.timestamp > startTimestamp) { revert IDOAlreadyStarted(); } price = _price; } function whitelist(address[] calldata accounts, bool[] calldata toWhitelist) external onlyOwner { for (uint256 i = 0; i < accounts.length; i++) { whitelisted[accounts[i]] = toWhitelist[i]; } } function withdrawSale() external onlyOwner { if (totalSale < softCap) { revert SoftCapNotReached(); } _sendBack(treasury, address(this).balance); } function rescueTokens(IERC20 token, uint256 amount, address to) external onlyOwner { token.transfer(to, amount); } function currentTax() public view returns (uint256) { if (block.timestamp < startTimestamp) { return startTaxPercent; } uint256 timePassed = block.timestamp - startTimestamp; uint256 totalFall = timePassed * taxPercentFallPerSec; if (totalFall < startTaxPercent) { return startTaxPercent - totalFall; } else { return 0; } } function calculateTaxedChiAmount(uint256 ethDeposit, uint256 tax) public view returns (uint256) { uint256 chiAmount = Math.mulDiv(ethDeposit, price, 1e18); if (tax > 0) { chiAmount -= Math.mulDiv(chiAmount, tax, 100_00000000); } return chiAmount; } function calculateChiAmountForAccount(address account, uint256 ethDeposit) public view returns (uint256) { if (whitelisted[account]) { return calculateTaxedChiAmount(ethDeposit, 0); } else { return calculateTaxedChiAmount(ethDeposit, currentTax()); } } function buy() external payable { if (block.timestamp < startTimestamp || block.timestamp > endTimestamp || totalSale == hardCap) { revert IDONotRunning(); } uint256 currAmount = ethAmount[msg.sender]; uint256 amountToBuy = Math.min(msg.value, Math.min(hardCap - totalSale, maxValue - currAmount)); if (amountToBuy == 0) { revert MaxValueReached(); } if (currAmount + amountToBuy < minValue) { revert MinValueNotReached(); } ethAmount[msg.sender] += amountToBuy; totalSale += amountToBuy; boughtChiAmount[msg.sender] += calculateChiAmountForAccount(msg.sender, amountToBuy); uint256 toReturn = msg.value - amountToBuy; _sendBack(msg.sender, toReturn); emit Buy(msg.sender, amountToBuy, ethAmount[msg.sender]); } function withdraw() external nonReentrant { if (totalSale >= softCap) { revert SoftCapReached(); } uint256 amount = ethAmount[msg.sender]; ethAmount[msg.sender] -= amount; totalSale -= amount; boughtChiAmount[msg.sender] = 0; _sendBack(msg.sender, amount); emit Withdraw(msg.sender, amount, ethAmount[msg.sender]); } function claim() external nonReentrant { if (block.timestamp < endTimestamp) { revert IDONotFinished(); } if (!claimingEnabled) { revert ClaimingNotEnabled(); } if (claimed[msg.sender]) { revert AlreadyClaimed(); } claimed[msg.sender] = true; uint256 chiAmount = boughtChiAmount[msg.sender]; chi.transfer(msg.sender, chiAmount); emit Claim(msg.sender, ethAmount[msg.sender], chiAmount); } function _sendBack(address to, uint256 amount) internal { if (amount != 0) { (bool success, ) = to.call{value: amount}(new bytes(0)); if (!success) { revert EtherSendFailed(msg.sender, amount); } } } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/utils/math/Math.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; import "../interfaces/ITimeWeightedBonding.sol"; import "../interfaces/IPriceFeedAggregator.sol"; import "../interfaces/IChiVesting.sol"; import "../library/ExternalContractAddresses.sol"; /// @title Contract handles buying CHI tokens for investor using time weighted bonding mechanism /// @notice This contract holds CHI tokens that are for sale /// @notice This contract sells tokens for discounted price and vests them through ChiVesting contract contract TimeWeightedBonding is ITimeWeightedBonding, Ownable { using SafeERC20 for IERC20; uint256 public constant BASE_PRICE = 10 ** 8; uint256 public constant MAX_LOCK_PERIOD = 208; // 208 epochs = 208 weeks = 4 years uint256 public constant EPOCH_DURATION = 1 weeks; address public constant WETH = ExternalContractAddresses.WETH; address public immutable treasury; IERC20 public immutable chi; IChiVesting public immutable chiVesting; IPriceFeedAggregator public immutable priceFeedAggregator; uint256 public cliffTimestampEnd; constructor( IERC20 _chi, IPriceFeedAggregator _priceFeedAggregator, IChiVesting _chiVesting, uint256 _cliffTimestampEnd, address _treasury ) Ownable() { chi = _chi; priceFeedAggregator = _priceFeedAggregator; chiVesting = _chiVesting; cliffTimestampEnd = _cliffTimestampEnd; treasury = _treasury; } /// @inheritdoc ITimeWeightedBonding function setCliffTimestampEnd(uint256 _cliffTimestampEnd) external onlyOwner { cliffTimestampEnd = _cliffTimestampEnd; emit SetCliffTimestampEnd(_cliffTimestampEnd); } /// @inheritdoc ITimeWeightedBonding function recoverChi(uint256 amount) external onlyOwner { chi.safeTransfer(msg.sender, amount); emit RecoverChi(msg.sender, amount); } /// @inheritdoc ITimeWeightedBonding function vest(address user, uint256 amount) external onlyOwner { chi.safeTransferFrom(msg.sender, address(chiVesting), amount); chiVesting.addVesting(user, amount); emit Vest(user, amount); } /// @inheritdoc ITimeWeightedBonding function buy(uint256 amount) external payable { uint256 ethPrice = _peek(WETH); uint256 chiPrice = _getDiscountedChiPrice(); uint256 ethCost = Math.mulDiv(amount, chiPrice, ethPrice); chi.safeTransfer(address(chiVesting), amount); chiVesting.addVesting(msg.sender, amount); _safeTransferETH(treasury, ethCost); _safeTransferETH(msg.sender, msg.value - ethCost); emit Buy(msg.sender, amount, ethCost); } function _getDiscountedChiPrice() private view returns (uint256) { uint256 cliffDuration = (cliffTimestampEnd - block.timestamp) / EPOCH_DURATION; uint256 discount = Math.mulDiv(cliffDuration, BASE_PRICE, MAX_LOCK_PERIOD); return Math.mulDiv(_peek(address(chi)), BASE_PRICE - discount, BASE_PRICE); } function _peek(address asset) private view returns (uint256) { uint256 price = priceFeedAggregator.peek(asset); return price; } function _safeTransferETH(address to, uint256 value) private { if (value != 0) { (bool success, ) = to.call{value: value}(new bytes(0)); if (!success) { revert EtherSendFailed(to, value); } } } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/utils/math/Math.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; import "../interfaces/ITimeWeightedBonding.sol"; import "../interfaces/IPriceFeedAggregator.sol"; import "../interfaces/IChiVesting.sol"; import "../library/ExternalContractAddresses.sol"; contract TimeWeightedOffering is Ownable { using SafeERC20 for IERC20; IERC20 public immutable chi; IChiVesting public immutable chiVesting; event Vest(address indexed user, uint256 amount); constructor(IERC20 _chi, IChiVesting _chiVesting) Ownable() { chi = _chi; chiVesting = _chiVesting; } function vest(address user, uint256 amount) external onlyOwner { chi.safeTransferFrom(msg.sender, address(chiVesting), amount); chiVesting.addVesting(user, amount); emit Vest(user, amount); } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import "@openzeppelin/contracts/utils/math/Math.sol"; import "../interfaces/IChiLocking.sol"; import "../library/ExternalContractAddresses.sol"; /// @title Contract for locking CHI tokens /// @notice This contract holds CHI tokens that are locked /// @notice Locking of reward distribution of CHI tokens is done per epoch contract ChiLocking is IChiLocking, OwnableUpgradeable { using SafeERC20 for IERC20; uint256 public constant MAX_LOCK_DURATION = 208; // 4 years in weeks IERC20 public constant stETH = IERC20(ExternalContractAddresses.stETH); IERC20 public chi; address public rewardController; uint256 public currentEpoch; uint256 public totalLockedChi; uint256 public totalUnlockedChi; uint256 public totalLockedShares; uint256 public totalVotingPower; uint256 public sumOfLockedDurations; uint256 public numberOfLockedPositions; uint256 public addVotingPowerInEpoch; uint256 public sumShareDurationProduct; uint256 public addedAmountInEpoch; mapping(address account => bool status) public chiLockers; mapping(address account => LockingData) public locks; mapping(uint256 id => EpochData) public epochs; modifier onlyRewardController() { if (msg.sender != rewardController) { revert NotRewardController(); } _; } modifier onlyChiLockers() { if (!chiLockers[msg.sender]) { revert NotChiLocker(); } _; } function initialize(IERC20 _chi, address _chiStaking) external initializer { __Ownable_init(); chiLockers[_chiStaking] = true; chi = _chi; currentEpoch = 1; } /// @inheritdoc IChiLocking function setUscStaking(address _uscStaking) external onlyOwner { chiLockers[_uscStaking] = true; emit SetUscStaking(_uscStaking); } /// @inheritdoc IChiLocking function setRewardController(address _rewardController) external onlyOwner { rewardController = _rewardController; emit SetRewardController(_rewardController); } /// @inheritdoc IChiLocking function setChiLocker(address contractAddress, bool toSet) external onlyOwner { chiLockers[contractAddress] = toSet; emit SetChiLocker(contractAddress, toSet); } /// @inheritdoc IChiLocking function getLockedPosition(address account, uint256 pos) external view returns (LockedPosition memory) { return locks[account].positions[pos]; } function getAllLockedPositions(address account) external view returns (AllLockedPositionsOutput[] memory out) { LockingData storage lockData = locks[account]; out = new AllLockedPositionsOutput[](lockData.positions.length); for (uint256 i = 0; i < lockData.positions.length; i++) { LockedPosition storage position = lockData.positions[i]; out[i].position = position; out[i].votingPower = _getCurrentVotingPowerForPosition(position); out[i].stETHreward = _getUnclaimedStETHPositionAmount(lockData.positions[i], lockData.lastUpdatedEpoch); out[i].totalAccumulatedChi = _totalAccumulatedAtEpoch(currentEpoch, lockData.positions[i]); out[i].totalChiRewards = (out[i].totalAccumulatedChi > position.amount) ? out[i].totalAccumulatedChi - position.amount : 0; } } /// @inheritdoc IChiLocking function getStakedChi() public view returns (uint256) { return totalLockedChi + totalUnlockedChi; } /// @inheritdoc IChiLocking function getLockedChi() public view returns (uint256) { return totalLockedChi; } /// @inheritdoc IChiLocking function getTotalVotingPower() external view returns (uint256) { return totalVotingPower; } /// @inheritdoc IChiLocking function availableChiWithdraw(address account) public view returns (uint256 availableTotal) { LockingData storage lockData = locks[account]; for (uint256 i = 0; i < lockData.positions.length; i++) { availableTotal += _availableToWithdrawFromPosition(lockData.positions[i]); } } /// @inheritdoc IChiLocking function lockChi(address account, uint256 amount, uint256 duration) external onlyChiLockers { if (amount == 0) { revert ZeroAmount(); } uint256 shares = _getNumberOfShares(amount); duration++; EpochData storage currentEpochData = epochs[currentEpoch]; EpochData storage afterEndEpoch = epochs[currentEpoch + duration]; locks[account].positions.push( LockedPosition({ amount: amount, startEpoch: currentEpoch, duration: duration, shares: shares, withdrawnChiAmount: 0 }) ); totalLockedChi += amount; totalLockedShares += shares; currentEpochData.lockedSharesInEpoch += shares; currentEpochData.totalLockedChiInEpoch += amount; afterEndEpoch.sharesToUnlock += shares; totalVotingPower += Math.mulDiv(amount, duration, MAX_LOCK_DURATION); sumShareDurationProduct += Math.mulDiv(shares, duration, MAX_LOCK_DURATION); sumOfLockedDurations += duration + 1; numberOfLockedPositions += 1; afterEndEpoch.numberOfEndingPositions += 1; emit LockChi(account, amount, shares, currentEpoch, currentEpoch + duration); } /// @inheritdoc IChiLocking function updateEpoch(uint256 chiEmissions, uint256 stETHrewards) external onlyRewardController { EpochData storage epoch = epochs[currentEpoch]; uint256 stETHrewardsForLocked; uint256 stakedChi = getStakedChi(); if (stakedChi != 0) { stETHrewardsForLocked = Math.mulDiv(stETHrewards, totalLockedChi, stakedChi); } uint256 stETHrewardsForUnlocked = stETHrewards - stETHrewardsForLocked; uint256 stEthPerLockedShare; if (epoch.lockedSharesInEpoch != 0) { stEthPerLockedShare = Math.mulDiv(stETHrewardsForLocked, 1e18, epoch.lockedSharesInEpoch); } epoch.cumulativeStETHPerLockedShare = epochs[currentEpoch - 1].cumulativeStETHPerLockedShare + stEthPerLockedShare; uint256 stEthPerUnlocked; if (totalUnlockedChi != 0) { stEthPerUnlocked = Math.mulDiv(stETHrewardsForUnlocked, 1e18, totalUnlockedChi); } epoch.cumulativeStETHPerUnlocked = epochs[currentEpoch - 1].cumulativeStETHPerUnlocked + stEthPerUnlocked; totalVotingPower -= totalLockedChi / MAX_LOCK_DURATION; sumShareDurationProduct -= totalLockedShares / MAX_LOCK_DURATION; if (totalLockedShares != 0) { totalVotingPower += Math.mulDiv(chiEmissions, sumShareDurationProduct, totalLockedShares); } totalLockedChi += chiEmissions; epoch.totalLockedChiInEpoch += chiEmissions; EpochData storage nextEpoch = epochs[currentEpoch + 1]; uint256 amountToUnlock; if (totalLockedShares != 0) { amountToUnlock = Math.mulDiv(epoch.totalLockedChiInEpoch, nextEpoch.sharesToUnlock, epoch.lockedSharesInEpoch); } totalLockedChi = totalLockedChi - amountToUnlock; totalUnlockedChi = totalUnlockedChi + amountToUnlock; totalLockedShares -= nextEpoch.sharesToUnlock; nextEpoch.lockedSharesInEpoch = totalLockedShares; nextEpoch.totalLockedChiInEpoch = totalLockedChi; currentEpoch++; emit UpdateEpoch(currentEpoch - 1, totalLockedChi, chiEmissions, stETHrewards, stEthPerLockedShare); } /// @inheritdoc IChiLocking function claimStETH(address account) external onlyRewardController returns (uint256 amount) { _updateUnclaimedStETH(account); amount = locks[account].unclaimedStETH; locks[account].unclaimedStETH = 0; emit ClaimStETH(account, amount); } /// @inheritdoc IChiLocking function withdrawChiFromAccount(address account, uint256 amount) public onlyChiLockers { withdrawChiFromAccountToAddress(account, account, amount); } /// @inheritdoc IChiLocking function withdrawChiFromAccountToAddress(address account, address toAddress, uint256 amount) public onlyChiLockers { if (amount == 0) { return; } _updateUnclaimedStETH(account); uint256 toWithdraw = amount; LockingData storage lockData = locks[account]; uint256 pos = 0; while (toWithdraw > 0) { if (pos >= lockData.positions.length) { revert UnavailableWithdrawAmount(amount); } LockedPosition storage position = lockData.positions[pos]; if (currentEpoch < position.startEpoch + position.duration) { pos++; } else { uint256 availableToWithdraw = _availableToWithdrawFromPosition(position); if (availableToWithdraw > toWithdraw) { position.withdrawnChiAmount += toWithdraw; toWithdraw = 0; } else { position.withdrawnChiAmount += toWithdraw; toWithdraw -= availableToWithdraw; _removePosition(lockData, pos); } } } totalUnlockedChi -= amount; chi.safeTransfer(toAddress, amount); emit WithdrawChiFromAccount(account, toAddress, amount); } /// @inheritdoc IChiLocking function unclaimedStETHAmount(address account) public view returns (uint256 totalAmount) { LockingData storage lockData = locks[account]; totalAmount = lockData.unclaimedStETH; if (lockData.lastUpdatedEpoch == currentEpoch) return totalAmount; for (uint256 i = 0; i < lockData.positions.length; i++) { totalAmount += _getUnclaimedStETHPositionAmount(lockData.positions[i], lockData.lastUpdatedEpoch); } } /// @inheritdoc IChiLocking function getVotingPower(address account) public view returns (uint256) { uint256 votingPower = 0; LockingData storage lockData = locks[account]; for (uint256 i = 0; i < lockData.positions.length; i++) { votingPower += _getCurrentVotingPowerForPosition(lockData.positions[i]); } return votingPower; } function _getNumberOfShares(uint256 chiAmount) internal view returns (uint256) { if (totalLockedChi == 0) return chiAmount; return Math.mulDiv(chiAmount, totalLockedShares, totalLockedChi); } function _updateUnclaimedStETH(address account) internal { locks[account].unclaimedStETH = unclaimedStETHAmount(account); locks[account].lastUpdatedEpoch = currentEpoch; } function _getUnclaimedStETHPositionAmount( LockedPosition storage position, uint256 lastUpdated ) internal view returns (uint256 unclaimedAmount) { if (lastUpdated == currentEpoch) return 0; uint256 fromEpoch = lastUpdated < position.startEpoch ? position.startEpoch : lastUpdated; uint256 toEpoch = currentEpoch - 1; uint256 lockEndsInEpoch = position.startEpoch + position.duration - 1; if (fromEpoch <= lockEndsInEpoch) { if (toEpoch <= lockEndsInEpoch) { unclaimedAmount += _unclaiemdStETHDuringLocked(position, fromEpoch, toEpoch); return unclaimedAmount; } else { unclaimedAmount += _unclaiemdStETHDuringLocked(position, fromEpoch, lockEndsInEpoch); } fromEpoch = lockEndsInEpoch + 1; } unclaimedAmount += _unclaiemdStETHAfterLocked(position, fromEpoch, toEpoch); } function _unclaiemdStETHDuringLocked( LockedPosition storage position, uint256 fromEpoch, uint256 toEpoch ) internal view returns (uint256) { uint256 rewardPerShare = epochs[toEpoch].cumulativeStETHPerLockedShare - epochs[fromEpoch - 1].cumulativeStETHPerLockedShare; return Math.mulDiv(rewardPerShare, position.shares, 1e18); } function _unclaiemdStETHAfterLocked( LockedPosition storage position, uint256 fromEpoch, uint256 toEpoch ) internal view returns (uint256) { uint256 unlockedChiAmount = _availableToWithdrawFromPosition(position); uint256 rewardPerChi = epochs[toEpoch].cumulativeStETHPerUnlocked - epochs[fromEpoch - 1].cumulativeStETHPerUnlocked; return Math.mulDiv(rewardPerChi, unlockedChiAmount, 1e18); } function _getCurrentVotingPowerForPosition(LockedPosition storage position) internal view returns (uint256) { if (currentEpoch >= position.startEpoch + position.duration || currentEpoch < position.startEpoch) return 0; uint256 epochsUntilEnd = position.startEpoch + position.duration - currentEpoch; if (currentEpoch == position.startEpoch) { return Math.mulDiv(position.amount, epochsUntilEnd, MAX_LOCK_DURATION); } else { return Math.mulDiv(_totalAccumulatedAtEpoch(currentEpoch - 1, position), epochsUntilEnd, MAX_LOCK_DURATION); } } function _availableToWithdrawFromPosition(LockedPosition storage position) internal view returns (uint256) { uint256 endLockingEpoch = position.startEpoch + position.duration - 1; if (currentEpoch <= endLockingEpoch) return 0; return _totalAccumulatedAtEpoch(endLockingEpoch, position) - position.withdrawnChiAmount; } function _totalAccumulatedAtEpoch(uint256 epochNum, LockedPosition storage position) internal view returns (uint256) { uint256 endEpoch = position.startEpoch + position.duration - 1; if (endEpoch < epochNum) epochNum = endEpoch; EpochData storage epoch = epochs[epochNum]; return Math.mulDiv(position.shares, epoch.totalLockedChiInEpoch, epoch.lockedSharesInEpoch); } function _removePosition(LockingData storage lockData, uint256 pos) internal { lockData.positions[pos] = lockData.positions[lockData.positions.length - 1]; lockData.positions.pop(); } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import "../interfaces/IChiStaking.sol"; import "../interfaces/IChiLocking.sol"; import "./StakingWithEpochs.sol"; /// @title Contract for staking chi tokens /// @notice This contract holds staked CHI tokens, staking is done per epoch /// @dev This contract is upgradeable contract ChiStaking is IChiStaking, OwnableUpgradeable, StakingWithEpochs { using SafeERC20 for IERC20; uint256 public constant MIN_LOCK_DURATION = 4; // 4 epochs = 4 weeks = 1 month uint256 public constant MAX_LOCK_DURATION = 208; // 208 epochs = 208 weeks = 4 years IChiLocking public chiLocking; function initialize(IERC20 _chiAddress) external initializer { __Ownable_init(); __StakingWithEpochs_init("Staked CHI", "stCHI", _chiAddress); } /// @inheritdoc IChiStaking function setChiLocking(IChiLocking _chiLocking) external onlyOwner { chiLocking = _chiLocking; emit SetChiLocking(address(_chiLocking)); } /// @inheritdoc IStaking function setRewardController(address _rewardController) external onlyOwner { _setRewardController(_rewardController); emit SetRewardController(_rewardController); } /// @inheritdoc IStaking function getStakedChi() external view returns (uint256) { return epochs[currentEpoch].shares; } /// @inheritdoc IChiStaking function updateEpoch(uint256 stETHrewards) external onlyRewardController { _updateCumulativeRewardsForToken(epochs[currentEpoch], epochs[currentEpoch - 1], RewardToken.STETH, stETHrewards); _updateEpoch(); } /// @inheritdoc IStakingWithEpochs function unstake(uint256 amount) public override(IStakingWithEpochs, StakingWithEpochs) { unstake(amount, msg.sender); } /// @inheritdoc IStakingWithEpochs function unstake(uint256 amount, address toAddress) public override(IStakingWithEpochs, StakingWithEpochs) { uint256 availableOnLocking = chiLocking.availableChiWithdraw(msg.sender); if (amount <= availableOnLocking) { chiLocking.withdrawChiFromAccountToAddress(msg.sender, toAddress, amount); } else { chiLocking.withdrawChiFromAccountToAddress(msg.sender, toAddress, availableOnLocking); StakingWithEpochs.unstake(amount - availableOnLocking, toAddress); } } /// @inheritdoc IChiStaking function lock(uint256 amount, uint256 duration, bool useStakedTokens) external { if (duration < MIN_LOCK_DURATION || duration > MAX_LOCK_DURATION) { revert InvalidDuration(duration); } if (!useStakedTokens) { stakeToken.safeTransferFrom(msg.sender, address(chiLocking), amount); chiLocking.lockChi(msg.sender, amount, duration); } else { unstake(amount, address(chiLocking)); chiLocking.lockChi(msg.sender, amount, duration + 1); } emit Lock(msg.sender, amount, duration, useStakedTokens); } /// @inheritdoc IStaking function claimStETH(address account) external onlyRewardController returns (uint256) { uint256 amount = _claimAndUpdateReward(account, RewardToken.STETH); emit ClaimStETH(account, amount); return amount; } /// @inheritdoc IStaking function unclaimedStETHAmount(address account) public view returns (uint256) { return _getCurrentReward(account, RewardToken.STETH); } function _calculatingRewards(RewardToken token) internal pure override returns (bool) { if (token == RewardToken.STETH) return true; return false; } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import "@openzeppelin/contracts/utils/math/Math.sol"; import "../interfaces/IChiVesting.sol"; /// @title Contract for vesting chi tokens /// @notice This contract holds chi tokens that are vested /// @notice This contract hold vested chi tokens, vesting is done on epochs /// @dev This contract is upgradeable contract ChiVesting is IChiVesting, OwnableUpgradeable { using SafeERC20 for IERC20; uint256 public constant MAX_LOCK_DURATION = 208; IERC20 public chi; uint256 public cliffDuration; uint256 public vestingDuration; uint256 public totalLockedChi; uint256 public totalUnlockedChi; uint256 public totalShares; uint256 public currentEpoch; uint256 public totalVotingPower; address public rewardController; mapping(address account => bool status) public chiVesters; mapping(address account => VestingData) public vestingData; mapping(uint256 id => EpochData) public epochs; modifier onlyRewardController() { if (msg.sender != rewardController) { revert NotRewardController(); } _; } modifier onlyChiVesters() { if (!chiVesters[msg.sender]) { revert NotChiVester(); } _; } function initialize(IERC20 _chi, uint256 _cliffDuration, uint256 _vestingDuration) external initializer { __Ownable_init(); chi = _chi; cliffDuration = _cliffDuration; vestingDuration = _vestingDuration; currentEpoch = 1; } /// @inheritdoc IChiVesting function setRewardController(address _rewardController) external onlyOwner { rewardController = _rewardController; emit SetRewardController(_rewardController); } /// @inheritdoc IChiVesting function setChiVester(address contractAddress, bool toSet) external onlyOwner { chiVesters[contractAddress] = toSet; emit SetChiVester(contractAddress, toSet); } /// @inheritdoc IChiVesting function getLockedChi() public view returns (uint256) { return totalLockedChi; } /// @inheritdoc IChiVesting function addVesting(address account, uint256 chiAmount) external onlyChiVesters { if (currentEpoch > cliffDuration) { revert CliffPassed(); } _updateUnclaimedStETH(account); uint256 shares = _getNumberOfShares(chiAmount); VestingData storage vesting = vestingData[account]; vesting.shares += shares; vesting.startAmount += chiAmount; totalLockedChi += chiAmount; totalShares += shares; totalVotingPower += Math.mulDiv(chiAmount, _epochsUntilEnd(), MAX_LOCK_DURATION); emit AddVesting(account, chiAmount, shares); } /// @inheritdoc IChiVesting function updateEpoch(uint256 chiEmissions, uint256 stETHrewards) external onlyRewardController { EpochData storage epoch = epochs[currentEpoch]; uint256 stETHRewardPerShare; if (totalShares != 0) { stETHRewardPerShare = Math.mulDiv(stETHrewards, 1e18, totalShares); } epoch.cumulativeStETHRewardPerShare = epochs[currentEpoch - 1].cumulativeStETHRewardPerShare + stETHRewardPerShare; uint256 epochsUntilEnd = _epochsUntilEnd(); totalLockedChi += chiEmissions; if (epochsUntilEnd > 0) { totalVotingPower += Math.mulDiv(chiEmissions, _epochsUntilEnd(), MAX_LOCK_DURATION); } uint256 amountToUnlock; if (totalLockedChi > 0 && currentEpoch > cliffDuration && epochsUntilEnd > 0) { amountToUnlock = totalLockedChi / _epochsUntilEnd(); } uint256 amountUnlockedPerShare; if (totalShares > 0) { amountUnlockedPerShare = Math.mulDiv(amountToUnlock, 1e18, totalShares); } epoch.cumulativeUnlockedPerShare = epochs[currentEpoch - 1].cumulativeUnlockedPerShare + amountUnlockedPerShare; totalLockedChi -= amountToUnlock; totalUnlockedChi += amountToUnlock; if (epochsUntilEnd > 0) { // decrease for unlocked tokens uint256 decreaseVotingPower = Math.mulDiv(amountToUnlock, epochsUntilEnd, MAX_LOCK_DURATION); if (decreaseVotingPower > totalVotingPower) { totalVotingPower = 0; } else { totalVotingPower -= decreaseVotingPower; } // regular decrease in voting power because of less time until end of locking totalVotingPower = Math.mulDiv(totalVotingPower, epochsUntilEnd - 1, epochsUntilEnd); } currentEpoch++; emit UpdateEpoch(currentEpoch - 1, stETHrewards, totalLockedChi); } /// @inheritdoc IChiVesting function withdrawChi(uint256 amount) external { VestingData storage vesting = vestingData[msg.sender]; _updateAvailabeWithdraw(msg.sender); if (amount > vesting.unlockedChi) { revert UnavailableWithdrawAmount(amount); } vesting.unlockedChi -= amount; totalUnlockedChi -= amount; chi.safeTransfer(msg.sender, amount); emit WithdrawChi(msg.sender, amount); } /// @inheritdoc IChiVesting function claimStETH(address account) external onlyRewardController returns (uint256) { _updateUnclaimedStETH(account); uint256 amount = vestingData[account].unclaimedStETH; vestingData[account].unclaimedStETH = 0; emit ClaimStETH(account, amount); return amount; } /// @inheritdoc IChiVesting function unclaimedStETHAmount(address account) public view returns (uint256) { VestingData storage vesting = vestingData[account]; uint256 totalAmount = vesting.unclaimedStETH; uint256 rewardPerShare = epochs[currentEpoch - 1].cumulativeStETHRewardPerShare - epochs[vesting.lastClaimedEpoch].cumulativeStETHRewardPerShare; totalAmount += Math.mulDiv(rewardPerShare, vesting.shares, 1e18); return totalAmount; } /// @inheritdoc IChiVesting function getVotingPower(address account) external view returns (uint256) { if (currentEpoch > cliffDuration + vestingDuration) return 0; VestingData storage vesting = vestingData[account]; return vesting.shares != 0 ? Math.mulDiv(totalVotingPower, vesting.shares, totalShares) : 0; } /// @inheritdoc IChiVesting function getTotalVotingPower() external view returns (uint256) { return totalVotingPower; } /// @inheritdoc IChiVesting function availableChiWithdraw(address account) public view returns (uint256) { VestingData storage vesting = vestingData[account]; return vesting.unlockedChi + _availableUnlockFromTo(vesting.lastWithdrawnEpoch, currentEpoch - 1, vesting.shares); } // includes available amount in `toEpoch` function _availableUnlockFromTo(uint256 fromEpoch, uint256 toEpoch, uint256 shares) internal view returns (uint256) { uint256 availableWithdrawPerShare = epochs[toEpoch].cumulativeUnlockedPerShare - epochs[fromEpoch].cumulativeUnlockedPerShare; return Math.mulDiv(availableWithdrawPerShare, shares, 1e18); } function _getNumberOfShares(uint256 chiAmount) internal view returns (uint256) { if (totalLockedChi == 0) return chiAmount; return Math.mulDiv(chiAmount, totalShares, totalLockedChi); } function _updateAvailabeWithdraw(address account) internal { vestingData[account].unlockedChi = availableChiWithdraw(account); vestingData[account].lastWithdrawnEpoch = currentEpoch - 1; } function _updateUnclaimedStETH(address account) internal { vestingData[account].unclaimedStETH = unclaimedStETHAmount(account); vestingData[account].lastClaimedEpoch = currentEpoch - 1; } function _epochsUntilEnd() internal view returns (uint256) { if (currentEpoch > cliffDuration + vestingDuration + 1) return 0; return cliffDuration + vestingDuration + 1 - currentEpoch; } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {ILockingManager} from "../interfaces/ILockingManager.sol"; contract LockingManager is ILockingManager, OwnableUpgradeable { struct LockInfo { uint256 amount; uint256 lockPeriod; uint256 startEpoch; uint256 endEpoch; uint256 lastClaimedEpoch; } IERC20 public lockToken; IERC20 public rewardToken; uint256 public constant MIN_LOCK_PERIOD = 1; uint256 public constant MAX_LOCK_PERIOD = 365; uint256 public constant REWARD_PER_EPOCH_BASE = 1e27; uint256 public epochStartTime; uint256 public epochDuration; uint256 public currentEpoch; uint256 public lastUpdatedEpoch; uint256 public rewardsPerEpoch; uint256 public totalLockedAmount; mapping(address => LockInfo[]) public userLocks; mapping(uint256 => uint256) public totalTimeWeightedSharesPerEpoch; mapping(uint256 => uint256) public totalTimeWeightedSharesUnlockedPerEpoch; mapping(address => uint256) public lastClaimedEpoch; mapping(uint256 => uint256) public cumulativeRewardsPerEpoch; modifier epochStarted() { if (block.timestamp < epochStartTime) { revert EpochNotStartedYet(); } _; } function initialize( IERC20 _lockToken, IERC20 _rewardToken, uint256 _epochStartTime, uint256 _epochDuration, uint256 _initialRewardsPerEpoch ) external initializer { __Ownable_init(); lockToken = _lockToken; rewardToken = _rewardToken; epochStartTime = _epochStartTime; epochDuration = _epochDuration; currentEpoch = 0; rewardsPerEpoch = _initialRewardsPerEpoch; lastUpdatedEpoch = 0; } function userLockingPositions(address user) external view returns (LockInfo[] memory) { return userLocks[user]; } function getUserBalance(address user) external view returns (uint256) { uint256 balance = 0; LockInfo[] memory locks = userLocks[user]; for (uint256 i = 0; i < locks.length; i++) { balance += locks[i].amount; } return balance; } function getUserRewards(address user) external view returns (uint256) { uint256 rewards = 0; LockInfo[] memory locks = userLocks[user]; for (uint256 i = 0; i < locks.length; i++) { rewards += calculateUserRewards(locks[i], locks[i].lastClaimedEpoch + 1, currentEpoch - 1); } return rewards; } function getUserRewards(address user, uint256 lockIndex) external view returns (uint256) { LockInfo[] memory locks = userLocks[user]; if (lockIndex >= locks.length) { revert InvalidLockIndex(); } return calculateUserRewards(locks[lockIndex], locks[lockIndex].lastClaimedEpoch + 1, currentEpoch - 1); } function lock(uint256 amount, uint256 lockPeriod) external epochStarted { _updateEpoch(); if (lockPeriod < MIN_LOCK_PERIOD || lockPeriod > MAX_LOCK_PERIOD) { revert InvalidLockPeriod(); } SafeERC20.safeTransferFrom(IERC20(lockToken), msg.sender, address(this), amount); uint256 endEpoch = currentEpoch + lockPeriod; LockInfo memory lockInfo = LockInfo({ amount: amount, lockPeriod: lockPeriod, startEpoch: currentEpoch + 1, endEpoch: endEpoch, lastClaimedEpoch: currentEpoch }); userLocks[msg.sender].push(lockInfo); uint256 timeWeightedShare = (amount * lockPeriod) / MAX_LOCK_PERIOD; totalTimeWeightedSharesPerEpoch[currentEpoch + 1] += timeWeightedShare; totalTimeWeightedSharesUnlockedPerEpoch[endEpoch + 1] += timeWeightedShare; totalLockedAmount += amount; emit LockCreated(msg.sender, amount, lockPeriod); } function withdraw(uint256 lockIndex) external epochStarted { _updateEpoch(); LockInfo[] storage locks = userLocks[msg.sender]; if (lockIndex >= locks.length) { revert InvalidLockIndex(); } LockInfo storage lockInfo = locks[lockIndex]; if (currentEpoch < lockInfo.endEpoch) { revert LockPeriodNotOver(); } SafeERC20.safeTransfer(IERC20(lockToken), msg.sender, lockInfo.amount); totalLockedAmount -= lockInfo.amount; userLocks[msg.sender][lockIndex].amount = 0; emit TokensUnlocked(msg.sender, lockInfo.amount, lockInfo.startEpoch, lockInfo.endEpoch); } function withdrawAllUnlockedPositions() external epochStarted { _updateEpoch(); uint256 unlockAmount = 0; LockInfo[] storage locks = userLocks[msg.sender]; for (uint256 i = 0; i < locks.length; i++) { if (currentEpoch >= locks[i].endEpoch) { unlockAmount += locks[i].amount; totalLockedAmount -= locks[i].amount; userLocks[msg.sender][i].amount = 0; } } SafeERC20.safeTransfer(IERC20(lockToken), msg.sender, unlockAmount); emit TokensUnlockedAllPositions(msg.sender, unlockAmount); } function calculateUserRewards( LockInfo memory userLock, uint256 fromEpoch, uint256 toEpoch ) internal view returns (uint256 rewards) { uint256 userPoints = 0; if (userLock.endEpoch >= fromEpoch && userLock.startEpoch <= toEpoch) { uint256 lockStart = userLock.startEpoch > fromEpoch ? userLock.startEpoch : fromEpoch; uint256 lockEnd = userLock.endEpoch < toEpoch ? userLock.endEpoch : toEpoch; for (uint256 epoch = lockStart; epoch <= lockEnd; epoch++) { uint256 timeWeightedShare = (userLock.amount * userLock.lockPeriod) / MAX_LOCK_PERIOD; if (totalTimeWeightedSharesPerEpoch[epoch] > 0) { userPoints += (timeWeightedShare * REWARD_PER_EPOCH_BASE) / totalTimeWeightedSharesPerEpoch[epoch]; } } uint256 totalRewards = (cumulativeRewardsPerEpoch[lockEnd] - cumulativeRewardsPerEpoch[lockStart - 1]) / (lockEnd - lockStart + 1); rewards += (totalRewards * userPoints) / REWARD_PER_EPOCH_BASE; } } function claimRewards() external epochStarted { _updateEpoch(); uint256 rewards = 0; for (uint256 i = 0; i < userLocks[msg.sender].length; i++) { uint256 fromEpoch = userLocks[msg.sender][i].lastClaimedEpoch + 1; uint256 toEpoch = currentEpoch - 1; if (fromEpoch > toEpoch) { revert NoRewardsToClaim(); } rewards += calculateUserRewards(userLocks[msg.sender][i], fromEpoch, toEpoch); userLocks[msg.sender][i].lastClaimedEpoch = toEpoch; } rewardToken.transfer(msg.sender, rewards); emit RewardsClaimed(msg.sender, rewards); } function claimRewards(uint256 lockIndex) external epochStarted { _updateEpoch(); if (lockIndex >= userLocks[msg.sender].length) { revert InvalidLockIndex(); } LockInfo storage lockInfo = userLocks[msg.sender][lockIndex]; uint256 fromEpoch = lockInfo.lastClaimedEpoch + 1; uint256 toEpoch = currentEpoch - 1; if (fromEpoch >= toEpoch) { revert NoRewardsToClaim(); } uint256 rewards = calculateUserRewards(lockInfo, fromEpoch, toEpoch); userLocks[msg.sender][lockIndex].lastClaimedEpoch = toEpoch; rewardToken.transfer(msg.sender, rewards); emit RewardsClaimed(msg.sender, rewards); } // when we update rewards per epoch, it will use updated value from next epoch // e.g. if we update rewards per epoch at epoch 10, it will use the updated value from epoch 11 function updateRewardsPerEpoch(uint256 newRewardsPerEpoch) external onlyOwner epochStarted { _updateEpoch(); rewardsPerEpoch = newRewardsPerEpoch; emit RewardsPerEpochUpdated(newRewardsPerEpoch); } function updateEpoch() public onlyOwner epochStarted { _updateEpoch(); } function _updateEpoch() internal { uint256 elapsedTime = block.timestamp - epochStartTime; uint256 newEpoch = elapsedTime / epochDuration + 1; if (newEpoch > currentEpoch) { for (uint256 i = currentEpoch + 1; i <= newEpoch; i++) { cumulativeRewardsPerEpoch[i] = cumulativeRewardsPerEpoch[i - 1] + rewardsPerEpoch; totalTimeWeightedSharesPerEpoch[i] += totalTimeWeightedSharesPerEpoch[i - 1] - totalTimeWeightedSharesUnlockedPerEpoch[i]; } currentEpoch = newEpoch; emit EpochUpdated(currentEpoch); } } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import "../interfaces/ILPStaking.sol"; import "../interfaces/IChiLocking.sol"; import "./StakingWithEpochs.sol"; import "../interfaces/IStakingManager.sol"; /// @title Contract for staking USC/ETH and CHI/ETH LP tokens /// @notice Staking is done in epochs /// @dev Contract is upgradeable contract LPStaking is ILPStaking, OwnableUpgradeable, StakingWithEpochs { using SafeERC20 for IERC20; uint256 public constant MIN_LOCK_DURATION = 4; uint256 public constant MAX_LOCK_DURATION = 208; IERC20 public chi; IChiLocking public chiLockingContract; uint256 public chiAmountFromEmissions; function initialize( IERC20 _chi, IChiLocking _chiLockingContract, IERC20 _token, string memory _name, string memory _symbol ) external initializer { __Ownable_init(); __StakingWithEpochs_init(_name, _symbol, _token); chi = _chi; chiLockingContract = _chiLockingContract; } function migrateToV2(uint256 amount, address recipient, address stakingManager) external { unstake(amount, address(this)); stakeToken.approve(stakingManager, amount); IStakingManager(stakingManager).stake(address(stakeToken), amount, recipient); } function migrateLpStaking(uint256 amount, address recipient, address stakingManager) external { unstake(amount, address(this)); stakeToken.approve(stakingManager, amount); IStakingManager(stakingManager).stake(address(stakeToken), amount, recipient); } /// @notice Sets reward controller contract address /// @param _rewardController Reward controller contract address function setRewardController(address _rewardController) external onlyOwner { _setRewardController(_rewardController); } /// @notice Updates epoch data /// @param chiEmissions Amount of CHI token incentives emitted in current epoch for USC stakers /// @custom:usage This function should be called from rewardController contract in purpose of updating epoch data function updateEpoch(uint256 chiEmissions, uint256 stETHrewards) external onlyRewardController { EpochData storage epoch = epochs[currentEpoch]; EpochData storage prevEpoch = epochs[currentEpoch - 1]; _updateCumulativeRewardsForToken(epoch, prevEpoch, RewardToken.CHI, chiEmissions); _updateCumulativeRewardsForToken(epoch, prevEpoch, RewardToken.STETH, stETHrewards); _updateEpoch(); chiAmountFromEmissions += chiEmissions; emit UpdateEpoch(currentEpoch - 1, chiEmissions); } /// @inheritdoc IStaking function claimStETH(address account) external onlyRewardController returns (uint256) { uint256 amount = _claimAndUpdateReward(account, RewardToken.STETH); emit ClaimStETH(account, amount); return amount; } /// @inheritdoc IStaking function getStakedChi() external view returns (uint256) { return chiAmountFromEmissions; } /// @inheritdoc IStaking function unclaimedStETHAmount(address account) public view returns (uint256) { return _getCurrentReward(account, RewardToken.STETH); } /// @notice Locks CHI tokens that user earned from incentives for given duration /// @param duration Locking duration in epochs function lockChi(uint256 duration) external { if (duration < MIN_LOCK_DURATION || duration > MAX_LOCK_DURATION) { revert InvalidDuration(duration); } uint256 amount = _claimAndUpdateReward(msg.sender, RewardToken.CHI); chiLockingContract.lockChi(msg.sender, amount, duration); chiAmountFromEmissions -= amount; chi.safeTransfer(address(chiLockingContract), amount); emit LockChi(msg.sender, amount, duration); } function _calculatingRewards(RewardToken token) internal pure override returns (bool) { if (token == RewardToken.CHI) return true; if (token == RewardToken.STETH) return true; return false; } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import "@openzeppelin/contracts/utils/math/Math.sol"; import "../interfaces/IRewardController.sol"; import "../interfaces/IMintableERC20.sol"; import "../interfaces/IBurnableERC20.sol"; import "../interfaces/IArbitrage.sol"; import "../interfaces/IReserveHolder.sol"; import "../interfaces/IStaking.sol"; import "../interfaces/IUSCStaking.sol"; import "../interfaces/IChiStaking.sol"; import "../interfaces/IChiLocking.sol"; import "../interfaces/IChiVesting.sol"; import "../interfaces/ILPStaking.sol"; /// @title Contract for managing rewards /// @notice This contract manages rewards for chi lockers, chi stakers, chi vesters and usc stakers /// @notice This contract holds chi incentives for all contracts and distributes then at the end of epoch /// @dev This contract is upgradeable contract RewardControllerV2 is IRewardController, OwnableUpgradeable { using SafeERC20 for IERC20; uint256 public constant EPOCH_DURATION = 1 weeks; IERC20 public chi; IERC20 public usc; IReserveHolder public reserveHolder; IArbitrage public arbitrager; IUSCStaking public uscStaking; IChiStaking public chiStaking; IChiLocking public chiLocking; IChiVesting public chiVesting; ILPStaking public uscEthLPStaking; ILPStaking public chiEthLPStaking; uint256 public currentEpoch; uint256 public firstEpochTimestamp; uint256 public chiIncentivesForChiLocking; uint256 public chiIncentivesForUscStaking; uint256 public chiIncentivesForUscEthLPStaking; uint256 public chiIncentivesForChiEthLPStaking; mapping(uint256 id => EpochData) public epochs; // Upgrade uint256 public constant MAX_PERCENTAGE = 100_00; uint256 public uscStakingProtocolFee; // Protocol fee from USC staking rewards, maximum 100_00 = 100% // Upgrade mapping(address => bool) public isArbitrager; // Upgrade uint256 public stEthPercentageForUscStaking; uint256 public stEthPercentageForChiStaking; uint256 public stEthPercentageForChiLocking; uint256 public stEthPercentageForChiVesting; uint256 public stEthPercentageForUscEthLPStaking; uint256 public stEthPercentageForChiEthLPStaking; modifier onlyArbitrager() { if (!isArbitrager[msg.sender] && address(arbitrager) != msg.sender) { revert NotArbitrager(); } _; } function initialize( IERC20 _chi, IERC20 _usc, IReserveHolder _reserveHolder, IUSCStaking _uscStaking, IChiStaking _chiStaking, IChiLocking _chiLocking, IChiVesting _chiVesting, ILPStaking _uscEthLPStaking, ILPStaking _chiEthLPStaking, uint256 _firstEpochTimestamp ) external initializer { __Ownable_init(); chi = _chi; usc = _usc; reserveHolder = _reserveHolder; uscStaking = _uscStaking; chiStaking = _chiStaking; chiLocking = _chiLocking; chiVesting = _chiVesting; uscEthLPStaking = _uscEthLPStaking; chiEthLPStaking = _chiEthLPStaking; firstEpochTimestamp = _firstEpochTimestamp; currentEpoch = 1; } /// @inheritdoc IRewardController function setStEthPercentages( uint256 _stEthPercentageForUscStaking, uint256 _stEthPercentageForChiStaking, uint256 _stEthPercentageForChiLocking, uint256 _stEthPercentageForChiVesting, uint256 _stEthPercentageForUscEthLPStaking, uint256 _stEthPercentageForChiEthLPStaking ) external onlyOwner { uint256 sumOfPercentages = _stEthPercentageForUscStaking + _stEthPercentageForChiStaking + _stEthPercentageForChiLocking + _stEthPercentageForChiVesting + _stEthPercentageForUscEthLPStaking + _stEthPercentageForChiEthLPStaking; if (sumOfPercentages != MAX_PERCENTAGE) { revert StEthYieldPercentagesNotCorrect(); } stEthPercentageForUscStaking = _stEthPercentageForUscStaking; stEthPercentageForChiStaking = _stEthPercentageForChiStaking; stEthPercentageForChiLocking = _stEthPercentageForChiLocking; stEthPercentageForChiVesting = _stEthPercentageForChiVesting; stEthPercentageForUscEthLPStaking = _stEthPercentageForUscEthLPStaking; stEthPercentageForChiEthLPStaking = _stEthPercentageForChiEthLPStaking; } /// @inheritdoc IRewardController function setChiIncentivesForChiLocking(uint256 _chiIncentivesForChiLocking) external onlyOwner { chiIncentivesForChiLocking = _chiIncentivesForChiLocking; } /// @inheritdoc IRewardController function setChiIncentivesForUscStaking(uint256 _chiIncentivesForUscStaking) external onlyOwner { chiIncentivesForUscStaking = _chiIncentivesForUscStaking; } /// @inheritdoc IRewardController function setChiIncentivesForUscEthLPStaking(uint256 _chiIncentivesForUscEthLPStaking) external onlyOwner { chiIncentivesForUscEthLPStaking = _chiIncentivesForUscEthLPStaking; } /// @inheritdoc IRewardController function setChiIncentivesForChiEthLPStaking(uint256 _chiIncentivesForChiEthLPStaking) external onlyOwner { chiIncentivesForChiEthLPStaking = _chiIncentivesForChiEthLPStaking; } /// @inheritdoc IRewardController function setArbitrager(IArbitrage _arbitrager) external onlyOwner { arbitrager = _arbitrager; emit SetArbitrager(address(_arbitrager)); } function updateArbitrager(address account, bool status) external onlyOwner { isArbitrager[account] = status; emit UpdateArbitrager(account, status); } function setUscStakingProtocolFee(uint256 _uscStakingProtocolFee) external onlyOwner { uscStakingProtocolFee = _uscStakingProtocolFee; } /// @inheritdoc IRewardController function rewardUSC(uint256 amount) external onlyArbitrager { if (amount == 0) { revert ZeroAmount(); } usc.safeTransferFrom(msg.sender, address(this), amount); epochs[currentEpoch].totalUscReward += amount; emit RewardUSC(msg.sender, amount); } /// @inheritdoc IRewardController function updateEpoch() public { if (block.timestamp < firstEpochTimestamp + currentEpoch * EPOCH_DURATION) { revert EpochNotFinished(); } if ( stEthPercentageForUscStaking + stEthPercentageForChiStaking + stEthPercentageForChiLocking + stEthPercentageForChiVesting + stEthPercentageForUscEthLPStaking + stEthPercentageForChiEthLPStaking != MAX_PERCENTAGE ) { revert StEthYieldPercentagesNotCorrect(); } uint256 totalUscRewards = epochs[currentEpoch].totalUscReward; uint256 uscProtocolFee = Math.mulDiv(totalUscRewards, uscStakingProtocolFee, MAX_PERCENTAGE); usc.safeTransfer(owner(), uscProtocolFee); epochs[currentEpoch].totalUscReward -= uscProtocolFee; StETHRewards memory stEthRewards = _updateAndGetStETHRewards(); ChiIncentives memory chiIncentives = _updateAndGetChiIncentives(); _updateEpochsInSubcontracts(stEthRewards, chiIncentives, epochs[currentEpoch].totalUscReward); usc.safeTransfer(address(uscStaking), epochs[currentEpoch].totalUscReward); chi.safeTransfer(address(uscStaking), chiIncentives.uscStakingChiIncentives); chi.safeTransfer(address(chiLocking), chiIncentives.chiLockingChiIncentives); chi.safeTransfer(address(chiVesting), chiIncentives.chiVestingChiIncentives); chi.safeTransfer(address(uscEthLPStaking), chiIncentivesForUscEthLPStaking); chi.safeTransfer(address(chiEthLPStaking), chiIncentivesForChiEthLPStaking); currentEpoch++; uint256 totalStEthRewards = stEthRewards.uscStakingStEthReward + stEthRewards.chiStakingStEthReward + stEthRewards.chiLockingStEthReward + stEthRewards.chiVestingStEthReward; uint256 totalChiIncentives = chiIncentives.uscStakingChiIncentives + chiIncentives.chiLockingChiIncentives + chiIncentives.chiVestingChiIncentives + chiIncentivesForUscEthLPStaking + chiIncentivesForChiEthLPStaking; emit UpdateEpoch(currentEpoch - 1, totalStEthRewards, totalChiIncentives); } /// @inheritdoc IRewardController function claimStEth() external { uint256 totalAmount; totalAmount += IStaking(address(uscStaking)).claimStETH(msg.sender); totalAmount += IStaking(address(chiStaking)).claimStETH(msg.sender); totalAmount += IStaking(address(chiLocking)).claimStETH(msg.sender); totalAmount += IStaking(address(chiVesting)).claimStETH(msg.sender); totalAmount += IStaking(address(uscEthLPStaking)).claimStETH(msg.sender); totalAmount += IStaking(address(chiEthLPStaking)).claimStETH(msg.sender); reserveHolder.claimRewards(msg.sender, totalAmount); emit ClaimStEth(msg.sender, totalAmount); } /// @inheritdoc IRewardController function unclaimedStETHAmount(address account) external view returns (uint256) { uint256 totalAmount; totalAmount += IStaking(address(uscStaking)).unclaimedStETHAmount(account); totalAmount += IStaking(address(chiStaking)).unclaimedStETHAmount(account); totalAmount += IStaking(address(chiLocking)).unclaimedStETHAmount(account); totalAmount += IStaking(address(chiVesting)).unclaimedStETHAmount(account); totalAmount += IStaking(address(uscEthLPStaking)).unclaimedStETHAmount(account); totalAmount += IStaking(address(chiEthLPStaking)).unclaimedStETHAmount(account); return totalAmount; } function _updateAndGetChiIncentives() internal view returns (ChiIncentives memory) { uint256 chiLockingLocked = chiLocking.getLockedChi(); uint256 chiVestingLocked = chiVesting.getLockedChi(); uint256 totalLockedChi = chiLockingLocked + chiVestingLocked; uint256 chiLockingChiIncentives; uint256 chiVestingChiIncentives; if (totalLockedChi != 0) { chiLockingChiIncentives = Math.mulDiv(chiLockingLocked, chiIncentivesForChiLocking, totalLockedChi); chiVestingChiIncentives = Math.mulDiv(chiVestingLocked, chiIncentivesForChiLocking, totalLockedChi); } return ChiIncentives({ uscStakingChiIncentives: chiIncentivesForUscStaking, chiLockingChiIncentives: chiLockingChiIncentives, chiVestingChiIncentives: chiVestingChiIncentives }); } function _updateEpochsInSubcontracts( StETHRewards memory stEthRewards, ChiIncentives memory chiIncentives, uint256 uscReward ) internal { uscStaking.updateEpoch(chiIncentives.uscStakingChiIncentives, uscReward, stEthRewards.uscStakingStEthReward); chiStaking.updateEpoch(stEthRewards.chiStakingStEthReward); chiLocking.updateEpoch(chiIncentives.chiLockingChiIncentives, stEthRewards.chiLockingStEthReward); chiVesting.updateEpoch(chiIncentives.chiVestingChiIncentives, stEthRewards.chiVestingStEthReward); uscEthLPStaking.updateEpoch(chiIncentivesForUscEthLPStaking, stEthRewards.uscEthLPStakingStEthReward); chiEthLPStaking.updateEpoch(chiIncentivesForChiEthLPStaking, stEthRewards.chiEthLPStakingStEthReward); } function _updateAndGetStETHRewards() internal returns (StETHRewards memory) { epochs[currentEpoch].reserveHolderTotalRewards = reserveHolder.getCumulativeRewards(); uint256 stETHEpochrewards = epochs[currentEpoch].reserveHolderTotalRewards - epochs[currentEpoch - 1].reserveHolderTotalRewards; uint256 uscStakingStEthReward = Math.mulDiv(stETHEpochrewards, stEthPercentageForUscStaking, MAX_PERCENTAGE); uint256 chiStakingStEthReward = Math.mulDiv(stETHEpochrewards, stEthPercentageForChiStaking, MAX_PERCENTAGE); uint256 chiLockingStEthReward = Math.mulDiv(stETHEpochrewards, stEthPercentageForChiLocking, MAX_PERCENTAGE); uint256 chiVestingStEthReward = Math.mulDiv(stETHEpochrewards, stEthPercentageForChiVesting, MAX_PERCENTAGE); uint256 uscEthLPStakingStEthReward = Math.mulDiv( stETHEpochrewards, stEthPercentageForUscEthLPStaking, MAX_PERCENTAGE ); uint256 chiEthLPStakingStEthReward = Math.mulDiv( stETHEpochrewards, stEthPercentageForChiEthLPStaking, MAX_PERCENTAGE ); return StETHRewards({ uscStakingStEthReward: uscStakingStEthReward, chiStakingStEthReward: chiStakingStEthReward, chiLockingStEthReward: chiLockingStEthReward, chiVestingStEthReward: chiVestingStEthReward, uscEthLPStakingStEthReward: uscEthLPStakingStEthReward, chiEthLPStakingStEthReward: chiEthLPStakingStEthReward }); } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import {IStakingManager} from "../interfaces/IStakingManager.sol"; contract StakedToken is ERC20Upgradeable, OwnableUpgradeable { /// @dev Address of staking contract address staking; /// @dev Address of staking token address stakingToken; constructor() { _disableInitializers(); } function initialize(address _staking, address _stakingToken, string memory _name, string memory _symbol) external initializer { __ERC20_init(_name, _symbol); __Ownable_init(); staking = _staking; stakingToken = _stakingToken; } function mint(address to, uint256 amount) external onlyOwner { _mint(to, amount); } function burn(address from, uint256 amount) external onlyOwner { _burn(from, amount); } function _beforeTokenTransfer(address sender, address recipient, uint256 amount) internal virtual override { IStakingManager(staking).updateHook(stakingToken, sender, recipient, amount); return super._beforeTokenTransfer(sender, recipient, amount); } receive() external payable {} }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "@openzeppelin/contracts/utils/Address.sol"; import "@openzeppelin/contracts/proxy/Proxy.sol"; import {IStakingManager} from "../interfaces/IStakingManager.sol"; contract StakedTokenBeaconProxy is Proxy { address private immutable _beacon; constructor(address beacon, bytes memory data) { _beacon = beacon; if (data.length > 0) { Address.functionDelegateCall(_implementation(), data); } } function _implementation() internal view virtual override returns (address) { return IStakingManager(_getBeacon()).getStakedTokenImplementation(); } function _getBeacon() internal view virtual returns (address) { return _beacon; } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import "@openzeppelin/contracts/utils/math/Math.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {IStakingManager} from "../interfaces/IStakingManager.sol"; import {ILockingManager} from "../interfaces/ILockingManager.sol"; import {RewardTokenData, RewardTokenConfig} from "../types/DataTypes.sol"; import {StakedToken} from "./StakedToken.sol"; import {IStakedToken} from "../interfaces/IStakedToken.sol"; import {StakedTokenBeaconProxy} from "./StakedTokenBeaconProxy.sol"; import "forge-std/console.sol"; /// @title StakingManger contract /// @notice Contract for staking tokens and earning multiple tokens as rewards /// @dev One contract handles multiple staking tokens and multiple reward tokens for each staking token contract StakingManager is IStakingManager, OwnableUpgradeable { /// @dev Constant that determines on how many decimals rewardPerStakedToken will be calculated and saved in storage /// @dev The more decimals this value has the more precision rewards will have /// @dev Nothing more than changing this value is needed in order to change precision uint256 public constant REWARD_PER_STAKED_TOKEN_BASE = 1e27; struct TokenInfo { /// @dev Address of staked token smart contract that is ERC20 representation of position in the pool /// @dev Staking tokens are hold in Staking smart contract /// @dev StakedToken is minted when user deposits tokens and burned when user withdraws tokens, user can transfer StakedToken to other users /// @dev If stakedToken is not zero address this means that staking token has already been whitelisted address stakedToken; /// @dev Address of LockingManager contract that is used for locking tokens /// @dev LockingManager contract is used for locking tokens, user can lock tokens for certain period of time and earn rewards ILockingManager lockingManager; /// @dev List of reward tokens, rewards are distributed for each token in the list /// @dev When new reward token is added, it is appended to the list, but it is not removed when reward token is removed address[] rewardTokens; /// @dev Data for each reward token, reward per staked token and last updated timestamp /// @dev This data is used to calculate rewards for each user and is updated on each interaction with the contract /// @dev Reward token address => reward token data mapping(address => RewardTokenData) rewardTokenData; /// @dev Emission per second for each reward token /// @dev Reward token address => emission per second mapping(address => RewardTokenConfig) rewardTokenConfig; /// @dev Reward debt for each user and reward token /// @dev Reward debt is used when calculating rewards for user, logic is copied from MasterChef contract /// @dev Reward debt is calculated on each interaction with the contract /// @dev Account => reward token => reward debt mapping(address => mapping(address => uint256)) rewardDebt; /// @dev Accrued rewards for each user and reward token, accrued rewards are not total rewards but only rewards that user had at the time of last interaction with the contract /// @dev Accrued rewards are calculated on each interaction with the contract, when user claims rewards accrued rewards are set to 0 /// @dev MasterChef contract sends rewards to user on each interaction, but this contract does not send rewards to user, user must claim rewards /// @dev Account => reward token => accrued rewards mapping(address => mapping(address => uint256)) accruedRewards; } /// @dev Mapping of token info for each staking token mapping(address => TokenInfo) tokenInfo; /// @dev Array of staking tokens, used to iterate over all staking tokens address[] stakingTokens; /// @dev Address of implementation contract for StakedToken smart contract /// @dev When this address is changed all StakedToken contract are automatically upgraded since they are BeaconProxy address stakedTokenImplementation; modifier onlyActiveStaking(address asset) { if (!isStakingStarted(asset)) { revert StakingNotStarted(); } _; } modifier onlyStakedToken(address stakingToken) { if (msg.sender != getStakedToken(stakingToken)) { revert NotStakedToken(); } _; } function initialize() external initializer { __Ownable_init(); } /// @inheritdoc IStakingManager function getStakedTokenImplementation() external view returns (address) { return stakedTokenImplementation; } /// @inheritdoc IStakingManager function getStakedToken(address stakingToken) public view returns (address) { return tokenInfo[stakingToken].stakedToken; } /// @inheritdoc IStakingManager function isStakingStarted(address asset) public view returns (bool) { return tokenInfo[asset].stakedToken != address(0); } /// @inheritdoc IStakingManager function getStakingTokens() external view returns (address[] memory) { return stakingTokens; } /// @inheritdoc IStakingManager function getRewardTokens(address stakingToken) external view returns (address[] memory) { return tokenInfo[stakingToken].rewardTokens; } /// @inheritdoc IStakingManager function getRewardTokenConfig( address stakingToken, address rewardToken ) external view returns (RewardTokenConfig memory rewardTokenConfig) { return tokenInfo[stakingToken].rewardTokenConfig[rewardToken]; } /// @inheritdoc IStakingManager function getRewardTokenData( address stakingToken, address rewardToken ) external view returns (RewardTokenData memory rewardTokenData) { return tokenInfo[stakingToken].rewardTokenData[rewardToken]; } /// @inheritdoc IStakingManager function getTotalStaked(address stakingToken) public view returns (uint256) { return IERC20(tokenInfo[stakingToken].stakedToken).totalSupply(); } /// @inheritdoc IStakingManager function getUserBalanceOnLockingManager(address user, address stakingToken) public view returns (uint256) { ILockingManager lockingManager = tokenInfo[stakingToken].lockingManager; return address(lockingManager) != address(0) ? lockingManager.getUserBalance(user) : 0; } /// @inheritdoc IStakingManager function getUserStakedBalance(address user, address stakingToken) public view returns (uint256) { return IERC20(tokenInfo[stakingToken].stakedToken).balanceOf(user) + getUserBalanceOnLockingManager(user, stakingToken); } /// @inheritdoc IStakingManager function getUserAccruedRewardsForToken( address user, address stakingToken, address rewardToken ) external view returns (uint256) { return tokenInfo[stakingToken].accruedRewards[user][rewardToken]; } /// @inheritdoc IStakingManager function getUserTotalRewardsForToken( address user, address stakingToken, address rewardToken ) public view returns (uint256) { TokenInfo storage stakingTokenInfo = tokenInfo[stakingToken]; RewardTokenData memory rewardTokenData = stakingTokenInfo.rewardTokenData[rewardToken]; uint256 totalStaked = getTotalStaked(stakingToken); uint256 userStakedBalance = getUserStakedBalance(user, stakingToken); uint256 rewardPerStakedToken = rewardTokenData.rewardPerStakedToken; if (rewardTokenData.lastUpdatedTimestamp < block.timestamp && totalStaked > 0) { uint256 tokenRewards = _getTotalPendingRewards(stakingToken, rewardToken); rewardPerStakedToken += Math.mulDiv(tokenRewards, REWARD_PER_STAKED_TOKEN_BASE, totalStaked); } uint256 currentRewards = stakingTokenInfo.accruedRewards[user][rewardToken]; uint256 pendingRewards = userStakedBalance * rewardPerStakedToken; return (currentRewards + pendingRewards - stakingTokenInfo.rewardDebt[user][rewardToken]) / REWARD_PER_STAKED_TOKEN_BASE; } /// @inheritdoc IStakingManager function setStakedTokenImplementation(address implementation) external { stakedTokenImplementation = implementation; emit SetStakedTokenImplementation(implementation); } /// @inheritdoc IStakingManager function setLockingManager(address stakingToken, address lockingManager) external onlyOwner { tokenInfo[stakingToken].lockingManager = ILockingManager(lockingManager); emit SetLockingManager(stakingToken, address(lockingManager)); } /// @inheritdoc IStakingManager function startStaking(address asset) external onlyOwner { if (isStakingStarted(asset)) { revert StakingAlreadyStarted(); } if (getStakedToken(asset) == address(0)) { StakedTokenBeaconProxy stakedTokenBeaconProxy = new StakedTokenBeaconProxy( address(this), abi.encodeWithSelector(StakedToken.initialize.selector, address(this), asset, "Staked Token", "STK") ); tokenInfo[asset].stakedToken = address(stakedTokenBeaconProxy); } stakingTokens.push(asset); emit StartStaking(asset); } /// @inheritdoc IStakingManager function configureRewardToken( address stakingToken, address rewardToken, RewardTokenConfig calldata config ) external onlyOwner onlyActiveStaking(stakingToken) { TokenInfo storage tokenInfo = tokenInfo[stakingToken]; address[] storage rewardTokenList = tokenInfo.rewardTokens; for (uint256 i = 0; i < rewardTokenList.length; i++) { if (rewardTokenList[i] == rewardToken) { _updateRewards(stakingToken, rewardToken); tokenInfo.rewardTokenConfig[rewardToken] = config; return; } } rewardTokenList.push(rewardToken); tokenInfo.rewardTokenConfig[rewardToken] = config; emit ConfigureRewardToken( stakingToken, rewardToken, config.startTimestamp, config.endTimestamp, config.emissionPerSecond ); } /// @inheritdoc IStakingManager function stake(address stakingToken, uint256 amount, address recipient) external onlyActiveStaking(stakingToken) { SafeERC20.safeTransferFrom(IERC20(stakingToken), msg.sender, address(this), amount); IStakedToken(getStakedToken(stakingToken)).mint(recipient, amount); emit Stake(stakingToken, msg.sender, recipient, amount); } /// @inheritdoc IStakingManager function unstake(address stakingToken, uint256 amount, address recipient) public { IStakedToken(getStakedToken(stakingToken)).burn(msg.sender, amount); SafeERC20.safeTransfer(IERC20(stakingToken), recipient, amount); emit Unstake(stakingToken, msg.sender, recipient, amount); } /// @inheritdoc IStakingManager function unstakeAndClaim(address stakingToken, uint256 amount, address recipient) external { unstake(stakingToken, amount, recipient); claimRewards(stakingToken, recipient); } /// @inheritdoc IStakingManager function claimRewards(address stakingToken, address recipient) public { TokenInfo storage tokenInfo = tokenInfo[stakingToken]; for (uint256 i = 0; i < tokenInfo.rewardTokens.length; i++) { claimRewardsForToken(stakingToken, tokenInfo.rewardTokens[i], recipient); } } /// @inheritdoc IStakingManager function claimRewardsForToken(address stakingToken, address rewardToken, address recipient) public { _updateRewards(stakingToken, rewardToken); uint256 userTotalRewards = getUserTotalRewardsForToken(msg.sender, stakingToken, rewardToken); TokenInfo storage tokenInfo = tokenInfo[stakingToken]; RewardTokenData memory rewardTokenData = tokenInfo.rewardTokenData[rewardToken]; uint256 userStakedBalance = getUserStakedBalance(msg.sender, stakingToken); tokenInfo.rewardDebt[msg.sender][rewardToken] = userStakedBalance * rewardTokenData.rewardPerStakedToken; tokenInfo.accruedRewards[msg.sender][rewardToken] = 0; SafeERC20.safeTransfer(IERC20(rewardToken), recipient, userTotalRewards); emit ClaimRewardsForToken(msg.sender, recipient, stakingToken, rewardToken); } /// @inheritdoc IStakingManager function updateHook( address stakingToken, address sender, address recipient, uint256 value ) external onlyStakedToken(stakingToken) { TokenInfo storage tokenInfo = tokenInfo[stakingToken]; address[] memory rewardTokens = tokenInfo.rewardTokens; uint256 senderCurrentBalance = getUserStakedBalance(sender, stakingToken); uint256 recipientCurrentBalance = getUserStakedBalance(recipient, stakingToken); for (uint256 i = 0; i < rewardTokens.length; i++) { address rewardToken = rewardTokens[i]; _updateRewards(stakingToken, rewardToken); // If sender is zero addrees, it means that this is mint operation which means that user is depositing so we don't need to update rewards for sender if (sender != address(0)) { _updateUserRewards(sender, stakingToken, rewardToken, senderCurrentBalance, senderCurrentBalance - value); } // If recipient is zero address, it means that this is burn operation which means that user is withdrawing so we don't need to update rewards for recipient if (recipient != address(0)) { _updateUserRewards( recipient, stakingToken, rewardToken, recipientCurrentBalance, recipientCurrentBalance + value ); } } } function _updateRewards(address stakingToken, address rewardToken) internal { TokenInfo storage tokenInfo = tokenInfo[stakingToken]; RewardTokenData storage rewardTokenData = tokenInfo.rewardTokenData[rewardToken]; uint256 totalStaked = getTotalStaked(stakingToken); if (totalStaked == 0) { rewardTokenData.lastUpdatedTimestamp = block.timestamp; return; } uint256 tokenRewards = _getTotalPendingRewards(stakingToken, rewardToken); rewardTokenData.rewardPerStakedToken += Math.mulDiv(tokenRewards, REWARD_PER_STAKED_TOKEN_BASE, totalStaked); rewardTokenData.lastUpdatedTimestamp = block.timestamp; } function _updateUserRewards( address user, address stakingToken, address rewardToken, uint256 userCurrentBalance, uint256 userFutureBalance ) internal { TokenInfo storage tokenInfo = tokenInfo[stakingToken]; RewardTokenData memory rewardTokenData = tokenInfo.rewardTokenData[rewardToken]; if (userCurrentBalance > 0) { // Calculate how much rewards user has accrued until now uint256 userAccruedRewards = userCurrentBalance * rewardTokenData.rewardPerStakedToken - tokenInfo.rewardDebt[user][rewardToken]; tokenInfo.accruedRewards[user][rewardToken] += userAccruedRewards; } // Update reward debt of user tokenInfo.rewardDebt[user][rewardToken] = userFutureBalance * rewardTokenData.rewardPerStakedToken; } function _getTotalPendingRewards(address stakingToken, address rewardToken) private view returns (uint256) { TokenInfo storage tokenInfo = tokenInfo[stakingToken]; RewardTokenData memory rewardTokenData = tokenInfo.rewardTokenData[rewardToken]; RewardTokenConfig memory rewardTokenConfig = tokenInfo.rewardTokenConfig[rewardToken]; uint256 fromTimestamp = Math.max(rewardTokenConfig.startTimestamp, rewardTokenData.lastUpdatedTimestamp); uint256 toTimestamp = Math.min(block.timestamp, rewardTokenConfig.endTimestamp); if (fromTimestamp > toTimestamp) { return 0; } uint256 timePassed = toTimestamp - fromTimestamp; uint256 emissionPerSecond = rewardTokenConfig.emissionPerSecond; return timePassed * emissionPerSecond; } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; import "@openzeppelin/contracts/utils/math/Math.sol"; import "../interfaces/IStakingWithEpochs.sol"; import "../interfaces/IMintableERC20.sol"; import "../interfaces/IBurnableERC20.sol"; import "../interfaces/IArbitrage.sol"; /// @title Contract for staking tokens /// @notice Staking logic is placed inside this contract /// @dev This contract is abstract and CHIStaking and USCStaking should inherit from him abstract contract StakingWithEpochs is IStakingWithEpochs, ERC20Upgradeable { using SafeERC20 for IERC20; IERC20 public stakeToken; address public rewardController; uint256 public currentEpoch; mapping(address account => StakeData) public stakes; mapping(uint256 id => EpochData) public epochs; modifier onlyRewardController() { if (msg.sender != rewardController) { revert NotRewardController(); } _; } function __StakingWithEpochs_init( string memory _name, string memory _symbol, IERC20 _stakeToken ) internal onlyInitializing { __ERC20_init(_name, _symbol); stakeToken = _stakeToken; currentEpoch = 1; } /// @inheritdoc IStakingWithEpochs function getUnclaimedRewards(address account, RewardToken token) external view returns (uint256) { return stakes[account].unclaimedRewards[token]; } /// @inheritdoc IStakingWithEpochs function getCumulativeRewardsPerShare(uint256 epoch, RewardToken token) external view returns (uint256) { return epochs[epoch].cumulativeRewardsPerShare[token]; } function unclaimedStChiAmount(address account) public view returns (uint256) { return _getCurrentReward(account, RewardToken.CHI); } /// @inheritdoc IStakingWithEpochs function stake(uint256 amount) external { if (amount == 0) { revert ZeroAmount(); } stakeToken.safeTransferFrom(msg.sender, address(this), amount); _updateUnclaimedRewards(msg.sender); stakes[msg.sender].addSharesNextEpoch += amount; epochs[currentEpoch + 1].shares += amount; _mint(msg.sender, amount); emit Stake(msg.sender, amount); } /// @inheritdoc IStakingWithEpochs function unstake(uint256 amount) public virtual { unstake(amount, msg.sender); } /// @inheritdoc IStakingWithEpochs function unstake(uint256 amount, address toAddress) public virtual { if (amount == 0) { revert ZeroAmount(); } StakeData storage stakeData = stakes[msg.sender]; uint256 addSharesNextEpoch = stakeData.addSharesNextEpoch; if (stakeData.shares + addSharesNextEpoch < amount) { revert AmountBelowStakedBalance(stakeData.shares + addSharesNextEpoch, amount); } _updateUnclaimedRewards(msg.sender); addSharesNextEpoch = stakeData.addSharesNextEpoch; if (addSharesNextEpoch > amount) { stakeData.addSharesNextEpoch -= amount; epochs[currentEpoch + 1].shares -= amount; } else { uint256 fromCurrentShares = amount - addSharesNextEpoch; stakeData.shares -= fromCurrentShares; epochs[currentEpoch].shares -= fromCurrentShares; epochs[currentEpoch + 1].shares -= addSharesNextEpoch; stakeData.addSharesNextEpoch = 0; } stakeToken.safeTransfer(toAddress, amount); _burn(msg.sender, amount); emit Unstake(msg.sender, toAddress, amount); } function _updateUnclaimedRewards(address account) internal { StakeData storage stakeData = stakes[account]; if (currentEpoch == stakeData.lastUpdatedEpoch) return; uint256 fromEpoch = stakeData.lastUpdatedEpoch > 0 ? stakeData.lastUpdatedEpoch - 1 : 0; _updateUnclaimedRewardsFromTo(stakeData, epochs[fromEpoch], epochs[currentEpoch - 1], stakeData.shares); if (stakeData.addSharesNextEpoch > 0) { _updateUnclaimedRewardsFromTo( stakeData, epochs[stakeData.lastUpdatedEpoch], epochs[currentEpoch - 1], stakeData.addSharesNextEpoch ); stakeData.shares += stakeData.addSharesNextEpoch; stakeData.addSharesNextEpoch = 0; } stakeData.lastUpdatedEpoch = currentEpoch; } function _updateUnclaimedRewardsFromTo( StakeData storage stakeData, EpochData storage fromEpoch, EpochData storage toEpoch, uint256 shares ) internal { for (uint8 i = 0; i <= uint8(type(RewardToken).max); i++) { RewardToken token = RewardToken(i); if (_calculatingRewards(token)) { stakeData.unclaimedRewards[token] += Math.mulDiv( toEpoch.cumulativeRewardsPerShare[token] - fromEpoch.cumulativeRewardsPerShare[token], shares, 1e18 ); } } } function _updateCumulativeRewardsForToken( EpochData storage epoch, EpochData storage prevEpoch, RewardToken token, uint256 amount ) internal { if (epoch.shares == 0) return; epoch.cumulativeRewardsPerShare[token] = prevEpoch.cumulativeRewardsPerShare[token] + Math.mulDiv(amount, 1e18, epoch.shares); } function _updateEpoch() internal { epochs[currentEpoch + 1].shares += epochs[currentEpoch].shares; currentEpoch++; } function _claimAndUpdateReward(address account, RewardToken token) internal returns (uint256) { _updateUnclaimedRewards(account); uint256 amount = stakes[account].unclaimedRewards[token]; stakes[account].unclaimedRewards[token] = 0; return amount; } function _getCurrentReward(address account, RewardToken token) public view returns (uint256) { StakeData storage stakeData = stakes[account]; uint256 totalAmount = stakeData.unclaimedRewards[token]; if (currentEpoch == stakeData.lastUpdatedEpoch) return totalAmount; totalAmount += Math.mulDiv( epochs[currentEpoch - 1].cumulativeRewardsPerShare[token] - epochs[stakeData.lastUpdatedEpoch - 1].cumulativeRewardsPerShare[token], stakeData.shares, 1e18 ); if (stakeData.addSharesNextEpoch > 0 && currentEpoch > stakeData.lastUpdatedEpoch + 1) { totalAmount += Math.mulDiv( epochs[currentEpoch - 1].cumulativeRewardsPerShare[token] - epochs[stakeData.lastUpdatedEpoch].cumulativeRewardsPerShare[token], stakeData.addSharesNextEpoch, 1e18 ); } return totalAmount; } function _setRewardController(address _rewardController) internal { rewardController = _rewardController; } function _calculatingRewards(RewardToken token) internal virtual returns (bool); uint256[50] private __gap; }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import "../interfaces/IUSCStaking.sol"; import "../interfaces/IArbitrage.sol"; import "../interfaces/IChiLocking.sol"; import "./StakingWithEpochs.sol"; import "../interfaces/ISTUSC.sol"; /// @title Contract for staking USC tokens /// @notice Staking is done in epochs /// @dev Contract is upgradeable contract USCStaking is IUSCStaking, OwnableUpgradeable, StakingWithEpochs { using SafeERC20 for IERC20; uint256 public constant MIN_LOCK_DURATION = 4; // 4 epochs = 4 weeks = 1 month uint256 public constant MAX_LOCK_DURATION = 208; // 208 epochs = 208 weeks = 4 years IERC20 public chi; IChiLocking public chiLockingContract; uint256 public chiAmountFromEmissions; function initialize(IERC20 _usc, IERC20 _chi, IChiLocking _chiLockingContract) external initializer { __Ownable_init(); __StakingWithEpochs_init("Staked USC", "stUSC", _usc); chi = _chi; chiLockingContract = _chiLockingContract; } function migrateToV2(uint256 amount, address recipient, address stUscAddress) external { unstake(amount, address(this)); ISTUSC(stUscAddress).stake(amount, recipient); } /// @inheritdoc IStaking function setRewardController(address _rewardController) external onlyOwner { _setRewardController(_rewardController); } /// @inheritdoc IUSCStaking function updateEpoch(uint256 chiEmissions, uint256 uscRewards, uint256 stETHrewards) external onlyRewardController { EpochData storage epoch = epochs[currentEpoch]; EpochData storage prevEpoch = epochs[currentEpoch - 1]; _updateCumulativeRewardsForToken(epoch, prevEpoch, RewardToken.CHI, chiEmissions); _updateCumulativeRewardsForToken(epoch, prevEpoch, RewardToken.USC, uscRewards); _updateCumulativeRewardsForToken(epoch, prevEpoch, RewardToken.STETH, stETHrewards); _updateEpoch(); chiAmountFromEmissions += chiEmissions; emit UpdateEpoch(currentEpoch - 1, chiEmissions, uscRewards, stETHrewards); } /// @inheritdoc IUSCStaking function lockChi(uint256 duration) external { if (duration < MIN_LOCK_DURATION || duration > MAX_LOCK_DURATION) { revert InvalidDuration(duration); } uint256 amount = _claimAndUpdateReward(msg.sender, RewardToken.CHI); chiLockingContract.lockChi(msg.sender, amount, duration); chiAmountFromEmissions -= amount; chi.safeTransfer(address(chiLockingContract), amount); emit LockChi(msg.sender, amount, duration); } /// @inheritdoc IUSCStaking function claimUSCRewards() external { uint256 amount = _claimAndUpdateReward(msg.sender, RewardToken.USC); stakeToken.safeTransfer(msg.sender, amount); emit ClaimUSCRewards(msg.sender, amount); } /// @inheritdoc IStaking function claimStETH(address account) external onlyRewardController returns (uint256) { uint256 amount = _claimAndUpdateReward(account, RewardToken.STETH); emit ClaimStETH(account, amount); return amount; } /// @inheritdoc IStaking function unclaimedStETHAmount(address account) public view returns (uint256) { return _getCurrentReward(account, RewardToken.STETH); } /// @inheritdoc IStaking function getStakedChi() external view returns (uint256) { return chiAmountFromEmissions; } function _calculatingRewards(RewardToken token) internal pure override returns (bool) { if (token == RewardToken.USC) return true; if (token == RewardToken.CHI) return true; if (token == RewardToken.STETH) return true; return false; } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import "../interfaces/IUSCStaking.sol"; import "../interfaces/IArbitrage.sol"; import "../interfaces/IChiLocking.sol"; import "./StakingWithEpochs.sol"; import "../interfaces/ISTUSC.sol"; /// @title Contract for staking USC tokens /// @notice Staking is done in epochs /// @dev Contract is upgradeable and used to rescue lost tokens frozen from epoch 1 contract USCStakingV2 is IUSCStaking, OwnableUpgradeable, StakingWithEpochs { using SafeERC20 for IERC20; uint256 public constant MIN_LOCK_DURATION = 4; // 4 epochs = 4 weeks = 1 month uint256 public constant MAX_LOCK_DURATION = 208; // 208 epochs = 208 weeks = 4 years IERC20 public chi; IChiLocking public chiLockingContract; uint256 public chiAmountFromEmissions; function initialize(IERC20 _usc, IERC20 _chi, IChiLocking _chiLockingContract) external initializer { __Ownable_init(); __StakingWithEpochs_init("Staked USC", "stUSC", _usc); chi = _chi; chiLockingContract = _chiLockingContract; } function initializeUscRescue() external reinitializer(3) { stakeToken.safeTransfer(0xcdB8d92FA641106fdAEe3CCC6B53a029eDb9c458, 326000 ether); } function initializeUscRewardBoots() external reinitializer(4) { EpochData storage epoch = epochs[currentEpoch - 1]; epoch.cumulativeRewardsPerShare[RewardToken.USC] += Math.mulDiv(3250 ether, 1e18, epoch.shares); } function migrateToV2(uint256 amount, address recipient, address stUscAddress) external { unstake(amount, address(this)); stakeToken.approve(stUscAddress, amount); ISTUSC(stUscAddress).stake(amount, recipient); } /// @inheritdoc IStaking function setRewardController(address _rewardController) external onlyOwner { _setRewardController(_rewardController); } /// @inheritdoc IUSCStaking function updateEpoch(uint256 chiEmissions, uint256 uscRewards, uint256 stETHrewards) external onlyRewardController { EpochData storage epoch = epochs[currentEpoch]; EpochData storage prevEpoch = epochs[currentEpoch - 1]; _updateCumulativeRewardsForToken(epoch, prevEpoch, RewardToken.CHI, chiEmissions); _updateCumulativeRewardsForToken(epoch, prevEpoch, RewardToken.USC, uscRewards); _updateCumulativeRewardsForToken(epoch, prevEpoch, RewardToken.STETH, stETHrewards); _updateEpoch(); chiAmountFromEmissions += chiEmissions; emit UpdateEpoch(currentEpoch - 1, chiEmissions, uscRewards, stETHrewards); } /// @inheritdoc IUSCStaking function lockChi(uint256 duration) external { if (duration < MIN_LOCK_DURATION || duration > MAX_LOCK_DURATION) { revert InvalidDuration(duration); } uint256 amount = _claimAndUpdateReward(msg.sender, RewardToken.CHI); chiLockingContract.lockChi(msg.sender, amount, duration); chiAmountFromEmissions -= amount; chi.safeTransfer(address(chiLockingContract), amount); emit LockChi(msg.sender, amount, duration); } /// @inheritdoc IUSCStaking function claimUSCRewards() external { uint256 amount = _claimAndUpdateReward(msg.sender, RewardToken.USC); stakeToken.safeTransfer(msg.sender, amount); emit ClaimUSCRewards(msg.sender, amount); } /// @inheritdoc IStaking function claimStETH(address account) external onlyRewardController returns (uint256) { uint256 amount = _claimAndUpdateReward(account, RewardToken.STETH); emit ClaimStETH(account, amount); return amount; } /// @inheritdoc IStaking function unclaimedStETHAmount(address account) public view returns (uint256) { return _getCurrentReward(account, RewardToken.STETH); } /// @inheritdoc IStaking function getStakedChi() external view returns (uint256) { return chiAmountFromEmissions; } function _calculatingRewards(RewardToken token) internal pure override returns (bool) { if (token == RewardToken.USC) return true; if (token == RewardToken.CHI) return true; if (token == RewardToken.STETH) return true; return false; } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PermitUpgradeable.sol"; import "@openzeppelin/contracts/utils/math/Math.sol"; import "./interfaces/ISTUSC.sol"; contract stUSC is ISTUSC, OwnableUpgradeable, ERC20PermitUpgradeable { using SafeERC20 for IERC20; uint256 public constant REWARD_PER_SHARE_BASE = 1e18; IERC20 public usc; uint256 public totalStaked; uint256 public totalShares; uint256 public startTimestamp; uint256 public lastUpdatedTimestamp; uint256 public emissionPerSecond; uint256 public rewardPerShare; mapping(address => uint256) public accruedRewards; mapping(address => uint256) public rewardDebt; mapping(address => uint256) public shares; function initialize(IERC20 _usc) external initializer { __Ownable_init_unchained(); __ERC20Permit_init_unchained("stUSC"); __ERC20_init_unchained("stUSC", "stUSC"); usc = _usc; } /// @inheritdoc ISTUSC function setUsc(IERC20 _usc) external onlyOwner { usc = _usc; } /// @inheritdoc ISTUSC function setEmissionPerSecond(uint256 _emissionPerSecond) external onlyOwner { _updateRewards(); emissionPerSecond = _emissionPerSecond; emit SetEmissionPerSecond(_emissionPerSecond); } /// @inheritdoc ISTUSC function setStartTimestamp(uint256 _startTimestamp) external onlyOwner { startTimestamp = _startTimestamp; emit SetStartTimestamp(_startTimestamp); } function tokenToShares(uint256 amount, Math.Rounding rounding) public view returns (uint256) { return totalShares > 0 ? Math.mulDiv(amount, totalShares, totalSupply(), rounding) : amount; } /// @inheritdoc ISTUSC function stake(uint256 amount, address recipient) external { usc.safeTransferFrom(msg.sender, address(this), amount); _transfer(address(0), recipient, amount); emit Stake(msg.sender, recipient, amount); } /// @inheritdoc ISTUSC function unstake(uint256 amount, address recipient) external { if (amount > balanceOf(msg.sender)) { revert InsufficientFunds(); } _transfer(msg.sender, address(0), amount); usc.safeTransfer(recipient, amount); emit Unstake(msg.sender, recipient, amount); } function balanceOf(address account) public view override returns (uint256) { return totalShares > 0 ? Math.mulDiv(shares[account], totalSupply(), totalShares) : 0; } function totalSupply() public view override returns (uint256) { return totalStaked + _getTotalPendingRewards(); } function _transfer(address from, address to, uint256 amount) internal override { uint256 senderCurrentShares = shares[from]; uint256 recipientCurrentShares = shares[to]; uint256 sendingShares = tokenToShares(amount, Math.Rounding.Up); uint256 receivingShares = tokenToShares(amount, Math.Rounding.Down); _updateRewards(); // If sender is zero addrees, it means that this is mint operation which means that user is depositing so we don't need to update rewards for sender if (from != address(0)) { _updateUserRewards(from, senderCurrentShares, senderCurrentShares - sendingShares); } // If recipient is zero address, it means that this is burn operation which means that user is withdrawing so we don't need to update rewards for recipient if (to != address(0)) { _updateUserRewards(to, recipientCurrentShares, recipientCurrentShares + receivingShares); } if (to != address(0)) { totalShares += receivingShares; totalStaked += amount; } if (from != address(0)) { totalShares -= sendingShares; totalStaked -= amount; } } function _updateRewards() internal { if (totalSupply() == 0) { lastUpdatedTimestamp = block.timestamp; return; } uint256 tokenRewards = _getTotalPendingRewards(); totalStaked += tokenRewards; if (totalShares > 0) rewardPerShare += Math.mulDiv(tokenRewards, REWARD_PER_SHARE_BASE, totalShares); lastUpdatedTimestamp = block.timestamp; } function _updateUserRewards(address user, uint256 userCurrentShares, uint256 userFutureShares) internal { if (userCurrentShares > 0) { // Calculate how much rewards user has accrued until now uint256 userAccruedRewards = userCurrentShares * rewardPerShare - rewardDebt[user]; accruedRewards[user] += userAccruedRewards; } // Update reward debt of user rewardDebt[user] = userFutureShares * rewardPerShare; shares[user] = userFutureShares; } function _getTotalPendingRewards() private view returns (uint256) { if (totalStaked == 0) { return 0; } uint256 fromTimestamp = Math.max(startTimestamp, lastUpdatedTimestamp); uint256 toTimestamp = block.timestamp; if (fromTimestamp > toTimestamp) { return 0; } uint256 timePassed = toTimestamp - fromTimestamp; return timePassed * emissionPerSecond; } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "../interfaces/ICHI.sol"; import "../interfaces/IToken.sol"; import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol"; import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol"; import "../common/Mintable.sol"; /// @title CHI token contract contract CHI is ICHI, ERC20Permit, ERC20Burnable, Mintable { constructor(uint256 initialSupply) ERC20("CHI", "CHI") ERC20Permit("CHI") { _mint(msg.sender, initialSupply); } /// @inheritdoc IToken function name() public pure override(IToken, ERC20) returns (string memory) { return "CHI"; } /// @inheritdoc IToken function symbol() public pure override(IToken, ERC20) returns (string memory) { return "CHI"; } /// @inheritdoc IToken function decimals() public pure override(IToken, ERC20) returns (uint8) { return 18; } /// @inheritdoc IToken function mint(address account, uint256 amount) external onlyMinter { _mint(account, amount); } /// @inheritdoc IToken function burn(uint256 amount) public override(IToken, ERC20Burnable) onlyMinter { super.burn(amount); } /// @inheritdoc ICHI function burnFrom(address account, uint256 amount) public override(ICHI, ERC20Burnable) onlyMinter { super.burnFrom(account, amount); } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "../interfaces/ICHI.sol"; import "../interfaces/IToken.sol"; import "../interfaces/IOptimismMintableERC20.sol"; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; /// @title CHI token contract for L2 contract CHIL2 is IOptimismMintableERC20, ERC20, ERC20Permit, Ownable { address public remoteToken; address public bridge; error NotBridge(); event SetBridge(address indexed bridge); event Mint(address indexed account, uint256 amount); event Burn(address indexed account, uint256 amount); modifier onlyBridge() { if (msg.sender != bridge) { revert NotBridge(); } _; } constructor(address _remoteToken, address _bridge) ERC20("CHI", "CHI") ERC20Permit("CHI") Ownable() { remoteToken = _remoteToken; bridge = _bridge; } function setBridge(address _bridge) external onlyOwner { bridge = _bridge; emit SetBridge(_bridge); } function mint(address account, uint256 amount) external onlyBridge { _mint(account, amount); emit Mint(account, amount); } function burn(address from, uint256 amount) public onlyBridge { _burn(from, amount); emit Burn(from, amount); } function supportsInterface(bytes4 _interfaceId) external pure virtual returns (bool) { bytes4 iface1 = type(IERC165).interfaceId; bytes4 iface3 = type(IOptimismMintableERC20).interfaceId; return _interfaceId == iface1 || _interfaceId == iface3; } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "../interfaces/IToken.sol"; import "../interfaces/IUSC.sol"; import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol"; import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol"; import "../common/Mintable.sol"; /// @title USC token contract contract USC is IUSC, ERC20Permit, ERC20Burnable, Mintable { constructor() ERC20("USC", "USC") ERC20Permit("USC") {} /// @inheritdoc IToken function name() public pure override(IToken, ERC20) returns (string memory) { return "USC"; } /// @inheritdoc IToken function symbol() public pure override(IToken, ERC20) returns (string memory) { return "USC"; } /// @inheritdoc IToken function decimals() public pure override(IToken, ERC20) returns (uint8) { return 18; } /// @inheritdoc IToken function mint(address account, uint256 amount) external onlyMinter { _mint(account, amount); } /// @inheritdoc IToken function burn(uint256 amount) public override(IToken, ERC20Burnable) onlyMinter { super.burn(amount); } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "../interfaces/ICHI.sol"; import "../interfaces/IToken.sol"; import "../interfaces/IOptimismMintableERC20.sol"; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; /// @title USC token contract for L2 contract USCL2 is IOptimismMintableERC20, ERC20, ERC20Permit, Ownable { address public remoteToken; address public bridge; error NotBridge(); event SetBridge(address indexed bridge); event Mint(address indexed account, uint256 amount); event Burn(address indexed account, uint256 amount); modifier onlyBridge() { if (msg.sender != bridge) { revert NotBridge(); } _; } constructor(address _remoteToken, address _bridge) ERC20("USC", "USC") ERC20Permit("USC") Ownable() { remoteToken = _remoteToken; bridge = _bridge; } function setBridge(address _bridge) external onlyOwner { bridge = _bridge; emit SetBridge(_bridge); } function mint(address account, uint256 amount) external onlyBridge { _mint(account, amount); emit Mint(account, amount); } function burn(address from, uint256 amount) public onlyBridge { _burn(from, amount); emit Burn(from, amount); } function supportsInterface(bytes4 _interfaceId) external pure virtual returns (bool) { bytes4 iface1 = type(IERC165).interfaceId; bytes4 iface3 = type(IOptimismMintableERC20).interfaceId; return _interfaceId == iface1 || _interfaceId == iface3; } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import "../interfaces/IChiLocking.sol"; import "../interfaces/IChiVesting.sol"; /// @title Voting Escrow CHI token contract /// @notice This contract is used by governance to determine voting power for users /// @notice Users get voting power by locking chi tokens contract veCHI is ERC20 { error NonTransferable(); IChiLocking public immutable chiLocking; IChiVesting public immutable chiVesting; constructor(IChiLocking _chiLocking, IChiVesting _chiVesting) ERC20("Voting Escrow CHI", "veCHI") { chiLocking = _chiLocking; chiVesting = _chiVesting; } /// @notice Returns balance/voting power of given account /// @param account Account to get balance/voting power for function balanceOf(address account) public view virtual override returns (uint256) { return chiLocking.getVotingPower(account) + chiVesting.getVotingPower(account); } /// @notice Returns total voting power in the protocol function totalSupply() public view virtual override returns (uint256) { return chiLocking.getTotalVotingPower() + chiVesting.getTotalVotingPower(); } /// @notice veCHI is not transferable/mintable/burnable function _beforeTokenTransfer(address, address, uint256) internal pure override { revert NonTransferable(); } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; import "contracts/interfaces/ITreasury.sol"; /// @title Treasury /// @notice This contract is responsible for keeping the tokens and distributing to other contracts for further rewards distribution contract Treasury is ITreasury, Ownable { using SafeERC20 for IERC20; constructor() Ownable() {} /// @inheritdoc ITreasury function transfer(address token, address destination, uint256 amount) external onlyOwner { if (token == address(0)) { _nativeTransfer(destination, amount); } else { _erc20Transfer(token, destination, amount); } } function _nativeTransfer(address destination, uint256 amount) internal { (bool success, ) = destination.call{value: amount}(new bytes(0)); if (!success) { revert EtherSendFailed(msg.sender, amount); } } function _erc20Transfer(address token, address destination, uint256 amount) internal { IERC20(token).safeTransfer(destination, amount); } fallback() external payable {} receive() external payable {} }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; /// @title DataTypes /// @notice Data types used accross the contracts /// @notice Data structure for reward token data struct RewardTokenData { /// @dev Reward per staked token, value is not neccessary on 18 decimals, base value is determined by REWARD_PER_STAKED_TOKEN_BASE constant /// @dev This value is used to calculate rewards for each user, the more decimals this value has the more precision rewards will have uint256 rewardPerStakedToken; /// @dev Last updated timestamp of rewards for this token, used to calculate accrued rewards in next interaction /// @dev This value can be different between reward tokens if no interaction happened after reward token is configured uint256 lastUpdatedTimestamp; } struct RewardTokenConfig { /// @dev Start timestamp of rewards for this token, used to calculate rewards for each user uint256 startTimestamp; /// @dev End timestamp of rewards for this token, used to calculate rewards for each user uint256 endTimestamp; /// @dev Emission per second for this token, used to calculate rewards for each user uint256 emissionPerSecond; }
// SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.0; // library copied from uniswap/lib/contracts/libraries/Babylonian.sol // the only change is in pragma solidity version // computes square roots using the babylonian method // https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method library Babylonian { // credit for this implementation goes to // https://github.com/abdk-consulting/abdk-libraries-solidity/blob/master/ABDKMath64x64.sol#L687 function sqrt(uint256 x) internal pure returns (uint256) { if (x == 0) return 0; // this block is equivalent to r = uint256(1) << (BitMath.mostSignificantBit(x) / 2); // however that code costs significantly more gas uint256 xx = x; uint256 r = 1; if (xx >= 0x100000000000000000000000000000000) { xx >>= 128; r <<= 64; } if (xx >= 0x10000000000000000) { xx >>= 64; r <<= 32; } if (xx >= 0x100000000) { xx >>= 32; r <<= 16; } if (xx >= 0x10000) { xx >>= 16; r <<= 8; } if (xx >= 0x100) { xx >>= 8; r <<= 4; } if (xx >= 0x10) { xx >>= 4; r <<= 2; } if (xx >= 0x8) { r <<= 1; } r = (r + x / r) >> 1; r = (r + x / r) >> 1; r = (r + x / r) >> 1; r = (r + x / r) >> 1; r = (r + x / r) >> 1; r = (r + x / r) >> 1; r = (r + x / r) >> 1; // Seven iterations should be enough uint256 r1 = x / r; return (r < r1 ? r : r1); } }
// SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.0; // library copied from uniswap/lib/contracts/libraries/BitMath.sol // the only change is in pragma solidity version library BitMath { // returns the 0 indexed position of the most significant bit of the input x // s.t. x >= 2**msb and x < 2**(msb+1) function mostSignificantBit(uint256 x) internal pure returns (uint8 r) { require(x > 0, "BitMath::mostSignificantBit: zero"); if (x >= 0x100000000000000000000000000000000) { x >>= 128; r += 128; } if (x >= 0x10000000000000000) { x >>= 64; r += 64; } if (x >= 0x100000000) { x >>= 32; r += 32; } if (x >= 0x10000) { x >>= 16; r += 16; } if (x >= 0x100) { x >>= 8; r += 8; } if (x >= 0x10) { x >>= 4; r += 4; } if (x >= 0x4) { x >>= 2; r += 2; } if (x >= 0x2) r += 1; } // returns the 0 indexed position of the least significant bit of the input x // s.t. (x & 2**lsb) != 0 and (x & (2**(lsb) - 1)) == 0) // i.e. the bit at the index is set and the mask of all lower bits is 0 function leastSignificantBit(uint256 x) internal pure returns (uint8 r) { require(x > 0, "BitMath::leastSignificantBit: zero"); r = 255; if (x & type(uint128).max > 0) { r -= 128; } else { x >>= 128; } if (x & type(uint64).max > 0) { r -= 64; } else { x >>= 64; } if (x & type(uint32).max > 0) { r -= 32; } else { x >>= 32; } if (x & type(uint16).max > 0) { r -= 16; } else { x >>= 16; } if (x & type(uint8).max > 0) { r -= 8; } else { x >>= 8; } if (x & 0xf > 0) { r -= 4; } else { x >>= 4; } if (x & 0x3 > 0) { r -= 2; } else { x >>= 2; } if (x & 0x1 > 0) r -= 1; } }
// SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.0; // library copied from uniswap/lib/contracts/libraries/FixedPoint.sol // the only change is in pragma solidity version import "./FullMath.sol"; import "./Babylonian.sol"; import "./BitMath.sol"; // a library for handling binary fixed point numbers (https://en.wikipedia.org/wiki/Q_(number_format)) library FixedPoint { // range: [0, 2**112 - 1] // resolution: 1 / 2**112 struct uq112x112 { uint224 _x; } // range: [0, 2**144 - 1] // resolution: 1 / 2**112 struct uq144x112 { uint256 _x; } uint8 public constant RESOLUTION = 112; uint256 public constant Q112 = 0x10000000000000000000000000000; // 2**112 uint256 private constant Q224 = 0x100000000000000000000000000000000000000000000000000000000; // 2**224 uint256 private constant LOWER_MASK = 0xffffffffffffffffffffffffffff; // decimal of UQ*x112 (lower 112 bits) // encode a uint112 as a UQ112x112 function encode(uint112 x) internal pure returns (uq112x112 memory) { return uq112x112(uint224(x) << RESOLUTION); } // encodes a uint144 as a UQ144x112 function encode144(uint144 x) internal pure returns (uq144x112 memory) { return uq144x112(uint256(x) << RESOLUTION); } // decode a UQ112x112 into a uint112 by truncating after the radix point function decode(uq112x112 memory self) internal pure returns (uint112) { return uint112(self._x >> RESOLUTION); } // decode a UQ144x112 into a uint144 by truncating after the radix point function decode144(uq144x112 memory self) internal pure returns (uint144) { return uint144(self._x >> RESOLUTION); } // multiply a UQ112x112 by a uint, returning a UQ144x112 // reverts on overflow function mul(uq112x112 memory self, uint256 y) internal pure returns (uq144x112 memory) { uint256 z = 0; require(y == 0 || (z = self._x * y) / y == self._x, "FixedPoint::mul: overflow"); return uq144x112(z); } // multiply a UQ112x112 by an int and decode, returning an int // reverts on overflow function muli(uq112x112 memory self, int256 y) internal pure returns (int256) { uint256 z = FullMath.mulDiv(self._x, uint256(y < 0 ? -y : y), Q112); require(z < 2 ** 255, "FixedPoint::muli: overflow"); return y < 0 ? -int256(z) : int256(z); } // multiply a UQ112x112 by a UQ112x112, returning a UQ112x112 // lossy function muluq(uq112x112 memory self, uq112x112 memory other) internal pure returns (uq112x112 memory) { if (self._x == 0 || other._x == 0) { return uq112x112(0); } uint112 upper_self = uint112(self._x >> RESOLUTION); // * 2^0 uint112 lower_self = uint112(self._x & LOWER_MASK); // * 2^-112 uint112 upper_other = uint112(other._x >> RESOLUTION); // * 2^0 uint112 lower_other = uint112(other._x & LOWER_MASK); // * 2^-112 // partial products uint224 upper = uint224(upper_self) * upper_other; // * 2^0 uint224 lower = uint224(lower_self) * lower_other; // * 2^-224 uint224 uppers_lowero = uint224(upper_self) * lower_other; // * 2^-112 uint224 uppero_lowers = uint224(upper_other) * lower_self; // * 2^-112 // so the bit shift does not overflow require(upper <= type(uint112).max, "FixedPoint::muluq: upper overflow"); // this cannot exceed 256 bits, all values are 224 bits uint256 sum = uint256(upper << RESOLUTION) + uppers_lowero + uppero_lowers + (lower >> RESOLUTION); // so the cast does not overflow require(sum <= type(uint224).max, "FixedPoint::muluq: sum overflow"); return uq112x112(uint224(sum)); } // divide a UQ112x112 by a UQ112x112, returning a UQ112x112 function divuq(uq112x112 memory self, uq112x112 memory other) internal pure returns (uq112x112 memory) { require(other._x > 0, "FixedPoint::divuq: division by zero"); if (self._x == other._x) { return uq112x112(uint224(Q112)); } if (self._x <= type(uint144).max) { uint256 value = (uint256(self._x) << RESOLUTION) / other._x; require(value <= type(uint224).max, "FixedPoint::divuq: overflow"); return uq112x112(uint224(value)); } uint256 result = FullMath.mulDiv(Q112, self._x, other._x); require(result <= type(uint224).max, "FixedPoint::divuq: overflow"); return uq112x112(uint224(result)); } // returns a UQ112x112 which represents the ratio of the numerator to the denominator // can be lossy function fraction(uint256 numerator, uint256 denominator) internal pure returns (uq112x112 memory) { require(denominator > 0, "FixedPoint::fraction: division by zero"); if (numerator == 0) return FixedPoint.uq112x112(0); if (numerator <= type(uint144).max) { uint256 result = (numerator << RESOLUTION) / denominator; require(result <= type(uint224).max, "FixedPoint::fraction: overflow"); return uq112x112(uint224(result)); } else { uint256 result = FullMath.mulDiv(numerator, Q112, denominator); require(result <= type(uint224).max, "FixedPoint::fraction: overflow"); return uq112x112(uint224(result)); } } // take the reciprocal of a UQ112x112 // reverts on overflow // lossy function reciprocal(uq112x112 memory self) internal pure returns (uq112x112 memory) { require(self._x != 0, "FixedPoint::reciprocal: reciprocal of zero"); require(self._x != 1, "FixedPoint::reciprocal: overflow"); return uq112x112(uint224(Q224 / self._x)); } // square root of a UQ112x112 // lossy between 0/1 and 40 bits function sqrt(uq112x112 memory self) internal pure returns (uq112x112 memory) { if (self._x <= type(uint144).max) { return uq112x112(uint224(Babylonian.sqrt(uint256(self._x) << 112))); } uint8 safeShiftBits = 255 - BitMath.mostSignificantBit(self._x); safeShiftBits -= safeShiftBits % 2; return uq112x112(uint224(Babylonian.sqrt(uint256(self._x) << safeShiftBits) << ((112 - safeShiftBits) / 2))); } }
// SPDX-License-Identifier: CC-BY-4.0 pragma solidity ^0.8.0; // library copied from uniswap/lib/contracts/libraries/FullMath.sol // the only change is in pragma solidity version // taken from https://medium.com/coinmonks/math-in-solidity-part-3-percents-and-proportions-4db014e080b1 // license is CC-BY-4.0 library FullMath { function fullMul(uint256 x, uint256 y) internal pure returns (uint256 l, uint256 h) { uint256 mm = mulmod(x, y, type(uint256).max); l = x * y; h = mm - l; if (mm < l) h -= 1; } function fullDiv(uint256 l, uint256 h, uint256 d) private pure returns (uint256) { uint256 pow2 = d & (~d + 1); d /= pow2; l /= pow2; l += h * ((~pow2 + 1) / pow2 + 1); uint256 r = 1; r *= 2 - d * r; r *= 2 - d * r; r *= 2 - d * r; r *= 2 - d * r; r *= 2 - d * r; r *= 2 - d * r; r *= 2 - d * r; r *= 2 - d * r; return l * r; } function mulDiv(uint256 x, uint256 y, uint256 d) internal pure returns (uint256) { (uint256 l, uint256 h) = fullMul(x, y); uint256 mm = mulmod(x, y, d); if (mm > l) h -= 1; l -= mm; if (h == 0) return l / d; require(h < d, "FullMath: FULLDIV_OVERFLOW"); return fullDiv(l, h, d); } }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.0; // library copied from uniswap/v2-core/contracts/libraries/SafeMath.sol // the only change is in pragma solidity version. // this contract was the reason of incompatible solidity version error // a library for performing overflow-safe math, courtesy of DappHub (https://github.com/dapphub/ds-math) library SafeMath { function add(uint x, uint y) internal pure returns (uint z) { require((z = x + y) >= x, "ds-math-add-overflow"); } function sub(uint x, uint y) internal pure returns (uint z) { require((z = x - y) <= x, "ds-math-sub-underflow"); } function mul(uint x, uint y) internal pure returns (uint z) { require(y == 0 || (z = x * y) / y == x, "ds-math-mul-overflow"); } }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.0; // library copied from uniswap/v2-core/contracts/libraries/UniswapV2Library.sol // the only change is in pragma solidity version. import "@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol"; import "./SafeMath.sol"; library UniswapV2Library { using SafeMath for uint; // returns sorted token addresses, used to handle return values from pairs sorted in this order function sortTokens(address tokenA, address tokenB) internal pure returns (address token0, address token1) { require(tokenA != tokenB, "UniswapV2Library: IDENTICAL_ADDRESSES"); (token0, token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); require(token0 != address(0), "UniswapV2Library: ZERO_ADDRESS"); } // calculates the CREATE2 address for a pair without making any external calls function pairFor(address factory, address tokenA, address tokenB) internal pure returns (address pair) { (address token0, address token1) = sortTokens(tokenA, tokenB); pair = address( uint160( uint( keccak256( abi.encodePacked( hex"ff", factory, keccak256(abi.encodePacked(token0, token1)), hex"96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f" // init code hash ) ) ) ) ); } // fetches and sorts the reserves for a pair function getReserves( address factory, address tokenA, address tokenB ) internal view returns (uint reserveA, uint reserveB) { (address token0, ) = sortTokens(tokenA, tokenB); (uint reserve0, uint reserve1, ) = IUniswapV2Pair(pairFor(factory, tokenA, tokenB)).getReserves(); (reserveA, reserveB) = tokenA == token0 ? (reserve0, reserve1) : (reserve1, reserve0); } // given some amount of an asset and pair reserves, returns an equivalent amount of the other asset function quote(uint amountA, uint reserveA, uint reserveB) internal pure returns (uint amountB) { require(amountA > 0, "UniswapV2Library: INSUFFICIENT_AMOUNT"); require(reserveA > 0 && reserveB > 0, "UniswapV2Library: INSUFFICIENT_LIQUIDITY"); amountB = amountA.mul(reserveB) / reserveA; } // given an input amount of an asset and pair reserves, returns the maximum output amount of the other asset function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) internal pure returns (uint amountOut) { require(amountIn > 0, "UniswapV2Library: INSUFFICIENT_INPUT_AMOUNT"); require(reserveIn > 0 && reserveOut > 0, "UniswapV2Library: INSUFFICIENT_LIQUIDITY"); uint amountInWithFee = amountIn.mul(997); uint numerator = amountInWithFee.mul(reserveOut); uint denominator = reserveIn.mul(1000).add(amountInWithFee); amountOut = numerator / denominator; } // given an output amount of an asset and pair reserves, returns a required input amount of the other asset function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) internal pure returns (uint amountIn) { require(amountOut > 0, "UniswapV2Library: INSUFFICIENT_OUTPUT_AMOUNT"); require(reserveIn > 0 && reserveOut > 0, "UniswapV2Library: INSUFFICIENT_LIQUIDITY"); uint numerator = reserveIn.mul(amountOut).mul(1000); uint denominator = reserveOut.sub(amountOut).mul(997); amountIn = (numerator / denominator).add(1); } // performs chained getAmountOut calculations on any number of pairs function getAmountsOut( address factory, uint amountIn, address[] memory path ) internal view returns (uint[] memory amounts) { require(path.length >= 2, "UniswapV2Library: INVALID_PATH"); amounts = new uint[](path.length); amounts[0] = amountIn; for (uint i; i < path.length - 1; i++) { (uint reserveIn, uint reserveOut) = getReserves(factory, path[i], path[i + 1]); amounts[i + 1] = getAmountOut(amounts[i], reserveIn, reserveOut); } } // performs chained getAmountIn calculations on any number of pairs function getAmountsIn( address factory, uint amountOut, address[] memory path ) internal view returns (uint[] memory amounts) { require(path.length >= 2, "UniswapV2Library: INVALID_PATH"); amounts = new uint[](path.length); amounts[amounts.length - 1] = amountOut; for (uint i = path.length - 1; i > 0; i--) { (uint reserveIn, uint reserveOut) = getReserves(factory, path[i - 1], path[i]); amounts[i - 1] = getAmountIn(amounts[i], reserveIn, reserveOut); } } }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.0; // library copied from uniswap/v2-core/contracts/libraries/UniswapV2OracleLibrary.sol // the only change is in pragma solidity version. import "@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol"; import "./FixedPoint.sol"; // library with helper methods for oracles that are concerned with computing average prices library UniswapV2OracleLibrary { using FixedPoint for *; // helper function that returns the current block timestamp within the range of uint32, i.e. [0, 2**32 - 1] function currentBlockTimestamp() internal view returns (uint32) { return uint32(block.timestamp % 2 ** 32); } // produces the cumulative price using counterfactuals to save gas and avoid a call to sync. function currentCumulativePrices( address pair ) internal view returns (uint price0Cumulative, uint price1Cumulative, uint32 blockTimestamp) { blockTimestamp = currentBlockTimestamp(); price0Cumulative = IUniswapV2Pair(pair).price0CumulativeLast(); price1Cumulative = IUniswapV2Pair(pair).price1CumulativeLast(); // if time has elapsed since the last update on the pair, mock the accumulated price values (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast) = IUniswapV2Pair(pair).getReserves(); if (blockTimestampLast != blockTimestamp) { // subtraction overflow is desired uint32 timeElapsed = blockTimestamp - blockTimestampLast; // addition overflow is desired // counterfactual price0Cumulative += uint(FixedPoint.fraction(reserve1, reserve0)._x) * timeElapsed; // counterfactual price1Cumulative += uint(FixedPoint.fraction(reserve0, reserve1)._x) * timeElapsed; } } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC4626Upgradeable.sol"; import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; contract wstUSC is ERC4626Upgradeable, OwnableUpgradeable { function initialize(address stUSC) external initializer { __ERC4626_init_unchained(IERC20Upgradeable(stUSC)); __ERC20_init_unchained("Wrapped Staked USC", "wstUSC"); __Ownable_init_unchained(); } function rescueTokens(IERC20 token, uint256 amount) external onlyOwner { token.transfer(msg.sender, amount); } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {ExternalContractAddresses} from "./library/ExternalContractAddresses.sol"; import {IArbitrageV5} from "./interfaces/IArbitrageV5.sol"; import {ISTUSC} from "./interfaces/ISTUSC.sol"; import {StakingManager} from "./staking/StakingManager.sol"; import {LockingManager} from "./staking/LockingManager.sol"; contract Zap { using SafeERC20 for IERC20; function zap(address tokenFrom, address tokenTo, uint256 amountIn, address receiver, bytes calldata data) external { IERC20(tokenFrom).safeTransferFrom(msg.sender, address(this), amountIn); IERC20(tokenFrom).approve(ExternalContractAddresses.ONE_INCH_ROUTER, amountIn); (bool success, ) = ExternalContractAddresses.ONE_INCH_ROUTER.call(data); require(success, "Zap: swap failed"); uint256 amountOut = IERC20(tokenTo).balanceOf(address(this)); IERC20(tokenTo).safeTransfer(receiver, amountOut); } function zapMint( address arbitrage, address tokenFrom, address tokenTo, uint256 amountIn, address receiver, bytes calldata data ) external { IERC20(tokenFrom).safeTransferFrom(msg.sender, address(this), amountIn); IERC20(tokenFrom).forceApprove(ExternalContractAddresses.ONE_INCH_ROUTER, amountIn); (bool success, ) = ExternalContractAddresses.ONE_INCH_ROUTER.call(data); require(success, "Zap: mint failed"); uint256 amountOut = IERC20(tokenTo).balanceOf(address(this)); IERC20(tokenTo).approve(arbitrage, amountOut); IArbitrageV5(arbitrage).mint(tokenTo, amountOut, receiver); } function zapMintStake( address arbitrage, address tokenFrom, address tokenTo, uint256 amountIn, address receiver, address staking, address usc, bytes calldata data ) external { IERC20(tokenFrom).safeTransferFrom(msg.sender, address(this), amountIn); IERC20(tokenFrom).forceApprove(ExternalContractAddresses.ONE_INCH_ROUTER, amountIn); (bool success, ) = ExternalContractAddresses.ONE_INCH_ROUTER.call(data); require(success, "Zap: mint failed"); uint256 amountOut = IERC20(tokenTo).balanceOf(address(this)); IERC20(tokenTo).approve(arbitrage, amountOut); IArbitrageV5(arbitrage).mint(tokenTo, amountOut, address(this)); uint256 mintedUscAmount = IERC20(usc).balanceOf(address(this)); IERC20(tokenTo).approve(staking, mintedUscAmount); ISTUSC(staking).stake(mintedUscAmount, receiver); } function mintAndStake( address arbitrage, address token, uint256 amount, address receiver, address staking, address usc ) external { IERC20(token).safeTransferFrom(msg.sender, address(this), amount); IERC20(token).approve(arbitrage, amount); IArbitrageV5(arbitrage).mint(token, amount, address(this)); uint256 mintedUscAmount = IERC20(usc).balanceOf(address(this)); IERC20(usc).approve(staking, mintedUscAmount); ISTUSC(staking).stake(mintedUscAmount, receiver); } function mintWithEthAndStake( address arbitrage, address receiver, address staking, address usc ) external payable { IArbitrageV5(arbitrage).mint{value: msg.value}(address(this)); uint256 mintedUscAmount = IERC20(usc).balanceOf(address(this)); IERC20(usc).approve(staking, mintedUscAmount); ISTUSC(staking).stake(mintedUscAmount, receiver); } //TODO: Remove this probably receive() external payable {} }
// SPDX-License-Identifier: MIT pragma solidity >=0.4.22 <0.9.0; library console { address constant CONSOLE_ADDRESS = address(0x000000000000000000636F6e736F6c652e6c6f67); function _sendLogPayload(bytes memory payload) private view { uint256 payloadLength = payload.length; address consoleAddress = CONSOLE_ADDRESS; /// @solidity memory-safe-assembly assembly { let payloadStart := add(payload, 32) let r := staticcall(gas(), consoleAddress, payloadStart, payloadLength, 0, 0) } } function log() internal view { _sendLogPayload(abi.encodeWithSignature("log()")); } function logInt(int p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(int)", p0)); } function logUint(uint p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint)", p0)); } function logString(string memory p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(string)", p0)); } function logBool(bool p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool)", p0)); } function logAddress(address p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(address)", p0)); } function logBytes(bytes memory p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bytes)", p0)); } function logBytes1(bytes1 p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bytes1)", p0)); } function logBytes2(bytes2 p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bytes2)", p0)); } function logBytes3(bytes3 p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bytes3)", p0)); } function logBytes4(bytes4 p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bytes4)", p0)); } function logBytes5(bytes5 p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bytes5)", p0)); } function logBytes6(bytes6 p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bytes6)", p0)); } function logBytes7(bytes7 p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bytes7)", p0)); } function logBytes8(bytes8 p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bytes8)", p0)); } function logBytes9(bytes9 p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bytes9)", p0)); } function logBytes10(bytes10 p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bytes10)", p0)); } function logBytes11(bytes11 p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bytes11)", p0)); } function logBytes12(bytes12 p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bytes12)", p0)); } function logBytes13(bytes13 p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bytes13)", p0)); } function logBytes14(bytes14 p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bytes14)", p0)); } function logBytes15(bytes15 p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bytes15)", p0)); } function logBytes16(bytes16 p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bytes16)", p0)); } function logBytes17(bytes17 p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bytes17)", p0)); } function logBytes18(bytes18 p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bytes18)", p0)); } function logBytes19(bytes19 p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bytes19)", p0)); } function logBytes20(bytes20 p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bytes20)", p0)); } function logBytes21(bytes21 p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bytes21)", p0)); } function logBytes22(bytes22 p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bytes22)", p0)); } function logBytes23(bytes23 p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bytes23)", p0)); } function logBytes24(bytes24 p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bytes24)", p0)); } function logBytes25(bytes25 p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bytes25)", p0)); } function logBytes26(bytes26 p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bytes26)", p0)); } function logBytes27(bytes27 p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bytes27)", p0)); } function logBytes28(bytes28 p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bytes28)", p0)); } function logBytes29(bytes29 p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bytes29)", p0)); } function logBytes30(bytes30 p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bytes30)", p0)); } function logBytes31(bytes31 p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bytes31)", p0)); } function logBytes32(bytes32 p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bytes32)", p0)); } function log(uint p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint)", p0)); } function log(string memory p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(string)", p0)); } function log(bool p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool)", p0)); } function log(address p0) internal view { _sendLogPayload(abi.encodeWithSignature("log(address)", p0)); } function log(uint p0, uint p1) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,uint)", p0, p1)); } function log(uint p0, string memory p1) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,string)", p0, p1)); } function log(uint p0, bool p1) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,bool)", p0, p1)); } function log(uint p0, address p1) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,address)", p0, p1)); } function log(string memory p0, uint p1) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,uint)", p0, p1)); } function log(string memory p0, string memory p1) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,string)", p0, p1)); } function log(string memory p0, bool p1) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,bool)", p0, p1)); } function log(string memory p0, address p1) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,address)", p0, p1)); } function log(bool p0, uint p1) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,uint)", p0, p1)); } function log(bool p0, string memory p1) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,string)", p0, p1)); } function log(bool p0, bool p1) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,bool)", p0, p1)); } function log(bool p0, address p1) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,address)", p0, p1)); } function log(address p0, uint p1) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,uint)", p0, p1)); } function log(address p0, string memory p1) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,string)", p0, p1)); } function log(address p0, bool p1) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,bool)", p0, p1)); } function log(address p0, address p1) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,address)", p0, p1)); } function log(uint p0, uint p1, uint p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,uint,uint)", p0, p1, p2)); } function log(uint p0, uint p1, string memory p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,uint,string)", p0, p1, p2)); } function log(uint p0, uint p1, bool p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,uint,bool)", p0, p1, p2)); } function log(uint p0, uint p1, address p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,uint,address)", p0, p1, p2)); } function log(uint p0, string memory p1, uint p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,string,uint)", p0, p1, p2)); } function log(uint p0, string memory p1, string memory p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,string,string)", p0, p1, p2)); } function log(uint p0, string memory p1, bool p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,string,bool)", p0, p1, p2)); } function log(uint p0, string memory p1, address p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,string,address)", p0, p1, p2)); } function log(uint p0, bool p1, uint p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,bool,uint)", p0, p1, p2)); } function log(uint p0, bool p1, string memory p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,bool,string)", p0, p1, p2)); } function log(uint p0, bool p1, bool p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,bool,bool)", p0, p1, p2)); } function log(uint p0, bool p1, address p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,bool,address)", p0, p1, p2)); } function log(uint p0, address p1, uint p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,address,uint)", p0, p1, p2)); } function log(uint p0, address p1, string memory p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,address,string)", p0, p1, p2)); } function log(uint p0, address p1, bool p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,address,bool)", p0, p1, p2)); } function log(uint p0, address p1, address p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,address,address)", p0, p1, p2)); } function log(string memory p0, uint p1, uint p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,uint,uint)", p0, p1, p2)); } function log(string memory p0, uint p1, string memory p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,uint,string)", p0, p1, p2)); } function log(string memory p0, uint p1, bool p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,uint,bool)", p0, p1, p2)); } function log(string memory p0, uint p1, address p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,uint,address)", p0, p1, p2)); } function log(string memory p0, string memory p1, uint p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,string,uint)", p0, p1, p2)); } function log(string memory p0, string memory p1, string memory p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,string,string)", p0, p1, p2)); } function log(string memory p0, string memory p1, bool p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,string,bool)", p0, p1, p2)); } function log(string memory p0, string memory p1, address p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,string,address)", p0, p1, p2)); } function log(string memory p0, bool p1, uint p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,bool,uint)", p0, p1, p2)); } function log(string memory p0, bool p1, string memory p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,bool,string)", p0, p1, p2)); } function log(string memory p0, bool p1, bool p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,bool,bool)", p0, p1, p2)); } function log(string memory p0, bool p1, address p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,bool,address)", p0, p1, p2)); } function log(string memory p0, address p1, uint p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,address,uint)", p0, p1, p2)); } function log(string memory p0, address p1, string memory p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,address,string)", p0, p1, p2)); } function log(string memory p0, address p1, bool p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,address,bool)", p0, p1, p2)); } function log(string memory p0, address p1, address p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,address,address)", p0, p1, p2)); } function log(bool p0, uint p1, uint p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,uint,uint)", p0, p1, p2)); } function log(bool p0, uint p1, string memory p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,uint,string)", p0, p1, p2)); } function log(bool p0, uint p1, bool p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,uint,bool)", p0, p1, p2)); } function log(bool p0, uint p1, address p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,uint,address)", p0, p1, p2)); } function log(bool p0, string memory p1, uint p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,string,uint)", p0, p1, p2)); } function log(bool p0, string memory p1, string memory p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,string,string)", p0, p1, p2)); } function log(bool p0, string memory p1, bool p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,string,bool)", p0, p1, p2)); } function log(bool p0, string memory p1, address p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,string,address)", p0, p1, p2)); } function log(bool p0, bool p1, uint p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,uint)", p0, p1, p2)); } function log(bool p0, bool p1, string memory p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,string)", p0, p1, p2)); } function log(bool p0, bool p1, bool p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,bool)", p0, p1, p2)); } function log(bool p0, bool p1, address p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,address)", p0, p1, p2)); } function log(bool p0, address p1, uint p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,address,uint)", p0, p1, p2)); } function log(bool p0, address p1, string memory p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,address,string)", p0, p1, p2)); } function log(bool p0, address p1, bool p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,address,bool)", p0, p1, p2)); } function log(bool p0, address p1, address p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,address,address)", p0, p1, p2)); } function log(address p0, uint p1, uint p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,uint,uint)", p0, p1, p2)); } function log(address p0, uint p1, string memory p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,uint,string)", p0, p1, p2)); } function log(address p0, uint p1, bool p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,uint,bool)", p0, p1, p2)); } function log(address p0, uint p1, address p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,uint,address)", p0, p1, p2)); } function log(address p0, string memory p1, uint p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,string,uint)", p0, p1, p2)); } function log(address p0, string memory p1, string memory p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,string,string)", p0, p1, p2)); } function log(address p0, string memory p1, bool p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,string,bool)", p0, p1, p2)); } function log(address p0, string memory p1, address p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,string,address)", p0, p1, p2)); } function log(address p0, bool p1, uint p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,bool,uint)", p0, p1, p2)); } function log(address p0, bool p1, string memory p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,bool,string)", p0, p1, p2)); } function log(address p0, bool p1, bool p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,bool,bool)", p0, p1, p2)); } function log(address p0, bool p1, address p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,bool,address)", p0, p1, p2)); } function log(address p0, address p1, uint p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,address,uint)", p0, p1, p2)); } function log(address p0, address p1, string memory p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,address,string)", p0, p1, p2)); } function log(address p0, address p1, bool p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,address,bool)", p0, p1, p2)); } function log(address p0, address p1, address p2) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,address,address)", p0, p1, p2)); } function log(uint p0, uint p1, uint p2, uint p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,uint,uint,uint)", p0, p1, p2, p3)); } function log(uint p0, uint p1, uint p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,uint,uint,string)", p0, p1, p2, p3)); } function log(uint p0, uint p1, uint p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,uint,uint,bool)", p0, p1, p2, p3)); } function log(uint p0, uint p1, uint p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,uint,uint,address)", p0, p1, p2, p3)); } function log(uint p0, uint p1, string memory p2, uint p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,uint,string,uint)", p0, p1, p2, p3)); } function log(uint p0, uint p1, string memory p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,uint,string,string)", p0, p1, p2, p3)); } function log(uint p0, uint p1, string memory p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,uint,string,bool)", p0, p1, p2, p3)); } function log(uint p0, uint p1, string memory p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,uint,string,address)", p0, p1, p2, p3)); } function log(uint p0, uint p1, bool p2, uint p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,uint,bool,uint)", p0, p1, p2, p3)); } function log(uint p0, uint p1, bool p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,uint,bool,string)", p0, p1, p2, p3)); } function log(uint p0, uint p1, bool p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,uint,bool,bool)", p0, p1, p2, p3)); } function log(uint p0, uint p1, bool p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,uint,bool,address)", p0, p1, p2, p3)); } function log(uint p0, uint p1, address p2, uint p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,uint,address,uint)", p0, p1, p2, p3)); } function log(uint p0, uint p1, address p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,uint,address,string)", p0, p1, p2, p3)); } function log(uint p0, uint p1, address p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,uint,address,bool)", p0, p1, p2, p3)); } function log(uint p0, uint p1, address p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,uint,address,address)", p0, p1, p2, p3)); } function log(uint p0, string memory p1, uint p2, uint p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,string,uint,uint)", p0, p1, p2, p3)); } function log(uint p0, string memory p1, uint p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,string,uint,string)", p0, p1, p2, p3)); } function log(uint p0, string memory p1, uint p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,string,uint,bool)", p0, p1, p2, p3)); } function log(uint p0, string memory p1, uint p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,string,uint,address)", p0, p1, p2, p3)); } function log(uint p0, string memory p1, string memory p2, uint p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,string,string,uint)", p0, p1, p2, p3)); } function log(uint p0, string memory p1, string memory p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,string,string,string)", p0, p1, p2, p3)); } function log(uint p0, string memory p1, string memory p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,string,string,bool)", p0, p1, p2, p3)); } function log(uint p0, string memory p1, string memory p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,string,string,address)", p0, p1, p2, p3)); } function log(uint p0, string memory p1, bool p2, uint p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,string,bool,uint)", p0, p1, p2, p3)); } function log(uint p0, string memory p1, bool p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,string,bool,string)", p0, p1, p2, p3)); } function log(uint p0, string memory p1, bool p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,string,bool,bool)", p0, p1, p2, p3)); } function log(uint p0, string memory p1, bool p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,string,bool,address)", p0, p1, p2, p3)); } function log(uint p0, string memory p1, address p2, uint p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,string,address,uint)", p0, p1, p2, p3)); } function log(uint p0, string memory p1, address p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,string,address,string)", p0, p1, p2, p3)); } function log(uint p0, string memory p1, address p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,string,address,bool)", p0, p1, p2, p3)); } function log(uint p0, string memory p1, address p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,string,address,address)", p0, p1, p2, p3)); } function log(uint p0, bool p1, uint p2, uint p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,bool,uint,uint)", p0, p1, p2, p3)); } function log(uint p0, bool p1, uint p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,bool,uint,string)", p0, p1, p2, p3)); } function log(uint p0, bool p1, uint p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,bool,uint,bool)", p0, p1, p2, p3)); } function log(uint p0, bool p1, uint p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,bool,uint,address)", p0, p1, p2, p3)); } function log(uint p0, bool p1, string memory p2, uint p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,bool,string,uint)", p0, p1, p2, p3)); } function log(uint p0, bool p1, string memory p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,bool,string,string)", p0, p1, p2, p3)); } function log(uint p0, bool p1, string memory p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,bool,string,bool)", p0, p1, p2, p3)); } function log(uint p0, bool p1, string memory p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,bool,string,address)", p0, p1, p2, p3)); } function log(uint p0, bool p1, bool p2, uint p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,bool,bool,uint)", p0, p1, p2, p3)); } function log(uint p0, bool p1, bool p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,bool,bool,string)", p0, p1, p2, p3)); } function log(uint p0, bool p1, bool p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,bool,bool,bool)", p0, p1, p2, p3)); } function log(uint p0, bool p1, bool p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,bool,bool,address)", p0, p1, p2, p3)); } function log(uint p0, bool p1, address p2, uint p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,bool,address,uint)", p0, p1, p2, p3)); } function log(uint p0, bool p1, address p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,bool,address,string)", p0, p1, p2, p3)); } function log(uint p0, bool p1, address p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,bool,address,bool)", p0, p1, p2, p3)); } function log(uint p0, bool p1, address p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,bool,address,address)", p0, p1, p2, p3)); } function log(uint p0, address p1, uint p2, uint p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,address,uint,uint)", p0, p1, p2, p3)); } function log(uint p0, address p1, uint p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,address,uint,string)", p0, p1, p2, p3)); } function log(uint p0, address p1, uint p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,address,uint,bool)", p0, p1, p2, p3)); } function log(uint p0, address p1, uint p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,address,uint,address)", p0, p1, p2, p3)); } function log(uint p0, address p1, string memory p2, uint p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,address,string,uint)", p0, p1, p2, p3)); } function log(uint p0, address p1, string memory p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,address,string,string)", p0, p1, p2, p3)); } function log(uint p0, address p1, string memory p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,address,string,bool)", p0, p1, p2, p3)); } function log(uint p0, address p1, string memory p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,address,string,address)", p0, p1, p2, p3)); } function log(uint p0, address p1, bool p2, uint p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,address,bool,uint)", p0, p1, p2, p3)); } function log(uint p0, address p1, bool p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,address,bool,string)", p0, p1, p2, p3)); } function log(uint p0, address p1, bool p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,address,bool,bool)", p0, p1, p2, p3)); } function log(uint p0, address p1, bool p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,address,bool,address)", p0, p1, p2, p3)); } function log(uint p0, address p1, address p2, uint p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,address,address,uint)", p0, p1, p2, p3)); } function log(uint p0, address p1, address p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,address,address,string)", p0, p1, p2, p3)); } function log(uint p0, address p1, address p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,address,address,bool)", p0, p1, p2, p3)); } function log(uint p0, address p1, address p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(uint,address,address,address)", p0, p1, p2, p3)); } function log(string memory p0, uint p1, uint p2, uint p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,uint,uint,uint)", p0, p1, p2, p3)); } function log(string memory p0, uint p1, uint p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,uint,uint,string)", p0, p1, p2, p3)); } function log(string memory p0, uint p1, uint p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,uint,uint,bool)", p0, p1, p2, p3)); } function log(string memory p0, uint p1, uint p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,uint,uint,address)", p0, p1, p2, p3)); } function log(string memory p0, uint p1, string memory p2, uint p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,uint,string,uint)", p0, p1, p2, p3)); } function log(string memory p0, uint p1, string memory p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,uint,string,string)", p0, p1, p2, p3)); } function log(string memory p0, uint p1, string memory p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,uint,string,bool)", p0, p1, p2, p3)); } function log(string memory p0, uint p1, string memory p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,uint,string,address)", p0, p1, p2, p3)); } function log(string memory p0, uint p1, bool p2, uint p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,uint,bool,uint)", p0, p1, p2, p3)); } function log(string memory p0, uint p1, bool p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,uint,bool,string)", p0, p1, p2, p3)); } function log(string memory p0, uint p1, bool p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,uint,bool,bool)", p0, p1, p2, p3)); } function log(string memory p0, uint p1, bool p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,uint,bool,address)", p0, p1, p2, p3)); } function log(string memory p0, uint p1, address p2, uint p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,uint,address,uint)", p0, p1, p2, p3)); } function log(string memory p0, uint p1, address p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,uint,address,string)", p0, p1, p2, p3)); } function log(string memory p0, uint p1, address p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,uint,address,bool)", p0, p1, p2, p3)); } function log(string memory p0, uint p1, address p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,uint,address,address)", p0, p1, p2, p3)); } function log(string memory p0, string memory p1, uint p2, uint p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,string,uint,uint)", p0, p1, p2, p3)); } function log(string memory p0, string memory p1, uint p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,string,uint,string)", p0, p1, p2, p3)); } function log(string memory p0, string memory p1, uint p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,string,uint,bool)", p0, p1, p2, p3)); } function log(string memory p0, string memory p1, uint p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,string,uint,address)", p0, p1, p2, p3)); } function log(string memory p0, string memory p1, string memory p2, uint p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,string,string,uint)", p0, p1, p2, p3)); } function log(string memory p0, string memory p1, string memory p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,string,string,string)", p0, p1, p2, p3)); } function log(string memory p0, string memory p1, string memory p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,string,string,bool)", p0, p1, p2, p3)); } function log(string memory p0, string memory p1, string memory p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,string,string,address)", p0, p1, p2, p3)); } function log(string memory p0, string memory p1, bool p2, uint p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,string,bool,uint)", p0, p1, p2, p3)); } function log(string memory p0, string memory p1, bool p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,string,bool,string)", p0, p1, p2, p3)); } function log(string memory p0, string memory p1, bool p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,string,bool,bool)", p0, p1, p2, p3)); } function log(string memory p0, string memory p1, bool p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,string,bool,address)", p0, p1, p2, p3)); } function log(string memory p0, string memory p1, address p2, uint p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,string,address,uint)", p0, p1, p2, p3)); } function log(string memory p0, string memory p1, address p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,string,address,string)", p0, p1, p2, p3)); } function log(string memory p0, string memory p1, address p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,string,address,bool)", p0, p1, p2, p3)); } function log(string memory p0, string memory p1, address p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,string,address,address)", p0, p1, p2, p3)); } function log(string memory p0, bool p1, uint p2, uint p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,bool,uint,uint)", p0, p1, p2, p3)); } function log(string memory p0, bool p1, uint p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,bool,uint,string)", p0, p1, p2, p3)); } function log(string memory p0, bool p1, uint p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,bool,uint,bool)", p0, p1, p2, p3)); } function log(string memory p0, bool p1, uint p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,bool,uint,address)", p0, p1, p2, p3)); } function log(string memory p0, bool p1, string memory p2, uint p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,bool,string,uint)", p0, p1, p2, p3)); } function log(string memory p0, bool p1, string memory p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,bool,string,string)", p0, p1, p2, p3)); } function log(string memory p0, bool p1, string memory p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,bool,string,bool)", p0, p1, p2, p3)); } function log(string memory p0, bool p1, string memory p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,bool,string,address)", p0, p1, p2, p3)); } function log(string memory p0, bool p1, bool p2, uint p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,bool,bool,uint)", p0, p1, p2, p3)); } function log(string memory p0, bool p1, bool p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,bool,bool,string)", p0, p1, p2, p3)); } function log(string memory p0, bool p1, bool p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,bool,bool,bool)", p0, p1, p2, p3)); } function log(string memory p0, bool p1, bool p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,bool,bool,address)", p0, p1, p2, p3)); } function log(string memory p0, bool p1, address p2, uint p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,bool,address,uint)", p0, p1, p2, p3)); } function log(string memory p0, bool p1, address p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,bool,address,string)", p0, p1, p2, p3)); } function log(string memory p0, bool p1, address p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,bool,address,bool)", p0, p1, p2, p3)); } function log(string memory p0, bool p1, address p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,bool,address,address)", p0, p1, p2, p3)); } function log(string memory p0, address p1, uint p2, uint p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,address,uint,uint)", p0, p1, p2, p3)); } function log(string memory p0, address p1, uint p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,address,uint,string)", p0, p1, p2, p3)); } function log(string memory p0, address p1, uint p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,address,uint,bool)", p0, p1, p2, p3)); } function log(string memory p0, address p1, uint p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,address,uint,address)", p0, p1, p2, p3)); } function log(string memory p0, address p1, string memory p2, uint p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,address,string,uint)", p0, p1, p2, p3)); } function log(string memory p0, address p1, string memory p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,address,string,string)", p0, p1, p2, p3)); } function log(string memory p0, address p1, string memory p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,address,string,bool)", p0, p1, p2, p3)); } function log(string memory p0, address p1, string memory p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,address,string,address)", p0, p1, p2, p3)); } function log(string memory p0, address p1, bool p2, uint p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,address,bool,uint)", p0, p1, p2, p3)); } function log(string memory p0, address p1, bool p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,address,bool,string)", p0, p1, p2, p3)); } function log(string memory p0, address p1, bool p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,address,bool,bool)", p0, p1, p2, p3)); } function log(string memory p0, address p1, bool p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,address,bool,address)", p0, p1, p2, p3)); } function log(string memory p0, address p1, address p2, uint p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,address,address,uint)", p0, p1, p2, p3)); } function log(string memory p0, address p1, address p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,address,address,string)", p0, p1, p2, p3)); } function log(string memory p0, address p1, address p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,address,address,bool)", p0, p1, p2, p3)); } function log(string memory p0, address p1, address p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(string,address,address,address)", p0, p1, p2, p3)); } function log(bool p0, uint p1, uint p2, uint p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,uint,uint,uint)", p0, p1, p2, p3)); } function log(bool p0, uint p1, uint p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,uint,uint,string)", p0, p1, p2, p3)); } function log(bool p0, uint p1, uint p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,uint,uint,bool)", p0, p1, p2, p3)); } function log(bool p0, uint p1, uint p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,uint,uint,address)", p0, p1, p2, p3)); } function log(bool p0, uint p1, string memory p2, uint p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,uint,string,uint)", p0, p1, p2, p3)); } function log(bool p0, uint p1, string memory p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,uint,string,string)", p0, p1, p2, p3)); } function log(bool p0, uint p1, string memory p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,uint,string,bool)", p0, p1, p2, p3)); } function log(bool p0, uint p1, string memory p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,uint,string,address)", p0, p1, p2, p3)); } function log(bool p0, uint p1, bool p2, uint p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,uint,bool,uint)", p0, p1, p2, p3)); } function log(bool p0, uint p1, bool p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,uint,bool,string)", p0, p1, p2, p3)); } function log(bool p0, uint p1, bool p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,uint,bool,bool)", p0, p1, p2, p3)); } function log(bool p0, uint p1, bool p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,uint,bool,address)", p0, p1, p2, p3)); } function log(bool p0, uint p1, address p2, uint p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,uint,address,uint)", p0, p1, p2, p3)); } function log(bool p0, uint p1, address p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,uint,address,string)", p0, p1, p2, p3)); } function log(bool p0, uint p1, address p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,uint,address,bool)", p0, p1, p2, p3)); } function log(bool p0, uint p1, address p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,uint,address,address)", p0, p1, p2, p3)); } function log(bool p0, string memory p1, uint p2, uint p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,string,uint,uint)", p0, p1, p2, p3)); } function log(bool p0, string memory p1, uint p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,string,uint,string)", p0, p1, p2, p3)); } function log(bool p0, string memory p1, uint p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,string,uint,bool)", p0, p1, p2, p3)); } function log(bool p0, string memory p1, uint p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,string,uint,address)", p0, p1, p2, p3)); } function log(bool p0, string memory p1, string memory p2, uint p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,string,string,uint)", p0, p1, p2, p3)); } function log(bool p0, string memory p1, string memory p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,string,string,string)", p0, p1, p2, p3)); } function log(bool p0, string memory p1, string memory p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,string,string,bool)", p0, p1, p2, p3)); } function log(bool p0, string memory p1, string memory p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,string,string,address)", p0, p1, p2, p3)); } function log(bool p0, string memory p1, bool p2, uint p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,string,bool,uint)", p0, p1, p2, p3)); } function log(bool p0, string memory p1, bool p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,string,bool,string)", p0, p1, p2, p3)); } function log(bool p0, string memory p1, bool p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,string,bool,bool)", p0, p1, p2, p3)); } function log(bool p0, string memory p1, bool p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,string,bool,address)", p0, p1, p2, p3)); } function log(bool p0, string memory p1, address p2, uint p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,string,address,uint)", p0, p1, p2, p3)); } function log(bool p0, string memory p1, address p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,string,address,string)", p0, p1, p2, p3)); } function log(bool p0, string memory p1, address p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,string,address,bool)", p0, p1, p2, p3)); } function log(bool p0, string memory p1, address p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,string,address,address)", p0, p1, p2, p3)); } function log(bool p0, bool p1, uint p2, uint p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,uint,uint)", p0, p1, p2, p3)); } function log(bool p0, bool p1, uint p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,uint,string)", p0, p1, p2, p3)); } function log(bool p0, bool p1, uint p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,uint,bool)", p0, p1, p2, p3)); } function log(bool p0, bool p1, uint p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,uint,address)", p0, p1, p2, p3)); } function log(bool p0, bool p1, string memory p2, uint p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,string,uint)", p0, p1, p2, p3)); } function log(bool p0, bool p1, string memory p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,string,string)", p0, p1, p2, p3)); } function log(bool p0, bool p1, string memory p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,string,bool)", p0, p1, p2, p3)); } function log(bool p0, bool p1, string memory p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,string,address)", p0, p1, p2, p3)); } function log(bool p0, bool p1, bool p2, uint p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,bool,uint)", p0, p1, p2, p3)); } function log(bool p0, bool p1, bool p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,bool,string)", p0, p1, p2, p3)); } function log(bool p0, bool p1, bool p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,bool,bool)", p0, p1, p2, p3)); } function log(bool p0, bool p1, bool p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,bool,address)", p0, p1, p2, p3)); } function log(bool p0, bool p1, address p2, uint p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,address,uint)", p0, p1, p2, p3)); } function log(bool p0, bool p1, address p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,address,string)", p0, p1, p2, p3)); } function log(bool p0, bool p1, address p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,address,bool)", p0, p1, p2, p3)); } function log(bool p0, bool p1, address p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,address,address)", p0, p1, p2, p3)); } function log(bool p0, address p1, uint p2, uint p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,address,uint,uint)", p0, p1, p2, p3)); } function log(bool p0, address p1, uint p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,address,uint,string)", p0, p1, p2, p3)); } function log(bool p0, address p1, uint p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,address,uint,bool)", p0, p1, p2, p3)); } function log(bool p0, address p1, uint p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,address,uint,address)", p0, p1, p2, p3)); } function log(bool p0, address p1, string memory p2, uint p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,address,string,uint)", p0, p1, p2, p3)); } function log(bool p0, address p1, string memory p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,address,string,string)", p0, p1, p2, p3)); } function log(bool p0, address p1, string memory p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,address,string,bool)", p0, p1, p2, p3)); } function log(bool p0, address p1, string memory p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,address,string,address)", p0, p1, p2, p3)); } function log(bool p0, address p1, bool p2, uint p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,address,bool,uint)", p0, p1, p2, p3)); } function log(bool p0, address p1, bool p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,address,bool,string)", p0, p1, p2, p3)); } function log(bool p0, address p1, bool p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,address,bool,bool)", p0, p1, p2, p3)); } function log(bool p0, address p1, bool p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,address,bool,address)", p0, p1, p2, p3)); } function log(bool p0, address p1, address p2, uint p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,address,address,uint)", p0, p1, p2, p3)); } function log(bool p0, address p1, address p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,address,address,string)", p0, p1, p2, p3)); } function log(bool p0, address p1, address p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,address,address,bool)", p0, p1, p2, p3)); } function log(bool p0, address p1, address p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(bool,address,address,address)", p0, p1, p2, p3)); } function log(address p0, uint p1, uint p2, uint p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,uint,uint,uint)", p0, p1, p2, p3)); } function log(address p0, uint p1, uint p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,uint,uint,string)", p0, p1, p2, p3)); } function log(address p0, uint p1, uint p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,uint,uint,bool)", p0, p1, p2, p3)); } function log(address p0, uint p1, uint p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,uint,uint,address)", p0, p1, p2, p3)); } function log(address p0, uint p1, string memory p2, uint p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,uint,string,uint)", p0, p1, p2, p3)); } function log(address p0, uint p1, string memory p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,uint,string,string)", p0, p1, p2, p3)); } function log(address p0, uint p1, string memory p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,uint,string,bool)", p0, p1, p2, p3)); } function log(address p0, uint p1, string memory p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,uint,string,address)", p0, p1, p2, p3)); } function log(address p0, uint p1, bool p2, uint p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,uint,bool,uint)", p0, p1, p2, p3)); } function log(address p0, uint p1, bool p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,uint,bool,string)", p0, p1, p2, p3)); } function log(address p0, uint p1, bool p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,uint,bool,bool)", p0, p1, p2, p3)); } function log(address p0, uint p1, bool p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,uint,bool,address)", p0, p1, p2, p3)); } function log(address p0, uint p1, address p2, uint p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,uint,address,uint)", p0, p1, p2, p3)); } function log(address p0, uint p1, address p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,uint,address,string)", p0, p1, p2, p3)); } function log(address p0, uint p1, address p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,uint,address,bool)", p0, p1, p2, p3)); } function log(address p0, uint p1, address p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,uint,address,address)", p0, p1, p2, p3)); } function log(address p0, string memory p1, uint p2, uint p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,string,uint,uint)", p0, p1, p2, p3)); } function log(address p0, string memory p1, uint p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,string,uint,string)", p0, p1, p2, p3)); } function log(address p0, string memory p1, uint p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,string,uint,bool)", p0, p1, p2, p3)); } function log(address p0, string memory p1, uint p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,string,uint,address)", p0, p1, p2, p3)); } function log(address p0, string memory p1, string memory p2, uint p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,string,string,uint)", p0, p1, p2, p3)); } function log(address p0, string memory p1, string memory p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,string,string,string)", p0, p1, p2, p3)); } function log(address p0, string memory p1, string memory p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,string,string,bool)", p0, p1, p2, p3)); } function log(address p0, string memory p1, string memory p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,string,string,address)", p0, p1, p2, p3)); } function log(address p0, string memory p1, bool p2, uint p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,string,bool,uint)", p0, p1, p2, p3)); } function log(address p0, string memory p1, bool p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,string,bool,string)", p0, p1, p2, p3)); } function log(address p0, string memory p1, bool p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,string,bool,bool)", p0, p1, p2, p3)); } function log(address p0, string memory p1, bool p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,string,bool,address)", p0, p1, p2, p3)); } function log(address p0, string memory p1, address p2, uint p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,string,address,uint)", p0, p1, p2, p3)); } function log(address p0, string memory p1, address p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,string,address,string)", p0, p1, p2, p3)); } function log(address p0, string memory p1, address p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,string,address,bool)", p0, p1, p2, p3)); } function log(address p0, string memory p1, address p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,string,address,address)", p0, p1, p2, p3)); } function log(address p0, bool p1, uint p2, uint p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,bool,uint,uint)", p0, p1, p2, p3)); } function log(address p0, bool p1, uint p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,bool,uint,string)", p0, p1, p2, p3)); } function log(address p0, bool p1, uint p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,bool,uint,bool)", p0, p1, p2, p3)); } function log(address p0, bool p1, uint p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,bool,uint,address)", p0, p1, p2, p3)); } function log(address p0, bool p1, string memory p2, uint p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,bool,string,uint)", p0, p1, p2, p3)); } function log(address p0, bool p1, string memory p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,bool,string,string)", p0, p1, p2, p3)); } function log(address p0, bool p1, string memory p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,bool,string,bool)", p0, p1, p2, p3)); } function log(address p0, bool p1, string memory p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,bool,string,address)", p0, p1, p2, p3)); } function log(address p0, bool p1, bool p2, uint p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,bool,bool,uint)", p0, p1, p2, p3)); } function log(address p0, bool p1, bool p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,bool,bool,string)", p0, p1, p2, p3)); } function log(address p0, bool p1, bool p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,bool,bool,bool)", p0, p1, p2, p3)); } function log(address p0, bool p1, bool p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,bool,bool,address)", p0, p1, p2, p3)); } function log(address p0, bool p1, address p2, uint p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,bool,address,uint)", p0, p1, p2, p3)); } function log(address p0, bool p1, address p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,bool,address,string)", p0, p1, p2, p3)); } function log(address p0, bool p1, address p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,bool,address,bool)", p0, p1, p2, p3)); } function log(address p0, bool p1, address p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,bool,address,address)", p0, p1, p2, p3)); } function log(address p0, address p1, uint p2, uint p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,address,uint,uint)", p0, p1, p2, p3)); } function log(address p0, address p1, uint p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,address,uint,string)", p0, p1, p2, p3)); } function log(address p0, address p1, uint p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,address,uint,bool)", p0, p1, p2, p3)); } function log(address p0, address p1, uint p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,address,uint,address)", p0, p1, p2, p3)); } function log(address p0, address p1, string memory p2, uint p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,address,string,uint)", p0, p1, p2, p3)); } function log(address p0, address p1, string memory p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,address,string,string)", p0, p1, p2, p3)); } function log(address p0, address p1, string memory p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,address,string,bool)", p0, p1, p2, p3)); } function log(address p0, address p1, string memory p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,address,string,address)", p0, p1, p2, p3)); } function log(address p0, address p1, bool p2, uint p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,address,bool,uint)", p0, p1, p2, p3)); } function log(address p0, address p1, bool p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,address,bool,string)", p0, p1, p2, p3)); } function log(address p0, address p1, bool p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,address,bool,bool)", p0, p1, p2, p3)); } function log(address p0, address p1, bool p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,address,bool,address)", p0, p1, p2, p3)); } function log(address p0, address p1, address p2, uint p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,address,address,uint)", p0, p1, p2, p3)); } function log(address p0, address p1, address p2, string memory p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,address,address,string)", p0, p1, p2, p3)); } function log(address p0, address p1, address p2, bool p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,address,address,bool)", p0, p1, p2, p3)); } function log(address p0, address p1, address p2, address p3) internal view { _sendLogPayload(abi.encodeWithSignature("log(address,address,address,address)", p0, p1, p2, p3)); } }
{ "optimizer": { "enabled": true, "runs": 1000 }, "viaIR": true, "evmVersion": "paris", "outputSelection": { "*": { "*": [ "evm.bytecode", "evm.deployedBytecode", "devdoc", "userdoc", "metadata", "abi" ] } }, "libraries": {} }
Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
Contract ABI
API[{"inputs":[],"name":"DAYS_IN_YEAR","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SECOND_IN_YEAR","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"chi","type":"address"},{"internalType":"contract LockingManager","name":"lockingManager","type":"address"},{"internalType":"contract StakingManager","name":"lpStaking","type":"address"},{"internalType":"contract IPriceFeedAggregator","name":"priceFeedAggregator","type":"address"},{"internalType":"contract IUniswapV2Pair","name":"uscEthLpToken","type":"address"},{"internalType":"contract IUniswapV2Pair","name":"chiEthLpToken","type":"address"}],"name":"chiEthLpLockingApr","outputs":[{"internalType":"uint256","name":"baseApr","type":"uint256"},{"internalType":"uint256","name":"extraApr","type":"uint256"},{"internalType":"uint256","name":"totalApr","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"chi","type":"address"},{"internalType":"contract ChiStaking","name":"chiStaking","type":"address"},{"internalType":"contract ChiLocking","name":"chiLocking","type":"address"},{"internalType":"contract USCStaking","name":"uscStaking","type":"address"},{"internalType":"contract LPStaking","name":"uscEthLpStaking","type":"address"},{"internalType":"contract LPStaking","name":"chiEthLpStaking","type":"address"},{"internalType":"contract ChiVesting","name":"chiVesting","type":"address"},{"internalType":"contract RewardControllerV2","name":"rewardController","type":"address"},{"internalType":"contract IPriceFeedAggregator","name":"priceFeedAggregator","type":"address"},{"internalType":"contract ReserveHolder","name":"reserveHolder","type":"address"}],"name":"chiLockingAPR","outputs":[{"internalType":"uint256","name":"totalApr","type":"uint256"},{"internalType":"uint256","name":"chiApr","type":"uint256"},{"internalType":"uint256","name":"stChiApr","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"chi","type":"address"},{"internalType":"contract ChiStaking","name":"chiStaking","type":"address"},{"internalType":"contract ChiLocking","name":"chiLocking","type":"address"},{"internalType":"contract USCStaking","name":"uscStaking","type":"address"},{"internalType":"contract LPStaking","name":"uscEthLpStaking","type":"address"},{"internalType":"contract LPStaking","name":"chiEthLpStaking","type":"address"},{"internalType":"contract ChiVesting","name":"chiVesting","type":"address"},{"internalType":"contract RewardControllerV2","name":"rewardController","type":"address"},{"internalType":"contract IPriceFeedAggregator","name":"priceFeedAggregator","type":"address"},{"internalType":"contract ReserveHolder","name":"reserveHolder","type":"address"}],"name":"chiStakingAPR","outputs":[{"internalType":"uint256","name":"chiStakingAprInStEth","type":"uint256"},{"internalType":"uint256","name":"chiLockingAprInStEth","type":"uint256"},{"internalType":"uint256","name":"uscStakingAprInStEth","type":"uint256"},{"internalType":"uint256","name":"uscEthLpStakingAprInStEth","type":"uint256"},{"internalType":"uint256","name":"chiEthLpStakingAprInStEth","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IUniswapV2Pair","name":"pair","type":"address"},{"internalType":"contract IPriceFeedAggregator","name":"priceFeedAggregator","type":"address"}],"name":"getLPTokenPrice","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"chi","type":"address"},{"internalType":"contract StakingManager","name":"lpStaking","type":"address"},{"internalType":"contract IPriceFeedAggregator","name":"priceFeedAggregator","type":"address"},{"internalType":"contract IUniswapV2Pair","name":"uscEthLpToken","type":"address"},{"internalType":"contract IUniswapV2Pair","name":"chiEthLpToken","type":"address"}],"name":"lpStakingApr","outputs":[{"internalType":"uint256","name":"stEthAprUscEthLp","type":"uint256"},{"internalType":"uint256","name":"weEthAprUscEthLp","type":"uint256"},{"internalType":"uint256","name":"chiAprUscEthLp","type":"uint256"},{"internalType":"uint256","name":"totalUscEthLpApr","type":"uint256"},{"internalType":"uint256","name":"stEthAprChiEthLp","type":"uint256"},{"internalType":"uint256","name":"weEthAprChiEthLp","type":"uint256"},{"internalType":"uint256","name":"chiAprChiEthLp","type":"uint256"},{"internalType":"uint256","name":"totalChiEthLpApr","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"chi","type":"address"},{"internalType":"contract LockingManager","name":"lockingManager","type":"address"},{"internalType":"contract StakingManager","name":"lpStaking","type":"address"},{"internalType":"contract IPriceFeedAggregator","name":"priceFeedAggregator","type":"address"},{"internalType":"contract IUniswapV2Pair","name":"uscEthLpToken","type":"address"},{"internalType":"contract IUniswapV2Pair","name":"chiEthLpToken","type":"address"}],"name":"uscEthLpLockingApr","outputs":[{"internalType":"uint256","name":"baseApr","type":"uint256"},{"internalType":"uint256","name":"extraApr","type":"uint256"},{"internalType":"uint256","name":"totalApr","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract stUSC","name":"_stUSC","type":"address"}],"name":"uscStakingApr","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract LockingManager","name":"lockingManager","type":"address"},{"internalType":"address","name":"chi","type":"address"},{"internalType":"contract stUSC","name":"_stUSC","type":"address"},{"internalType":"contract IPriceFeedAggregator","name":"priceFeedAggregator","type":"address"}],"name":"wstUscLockingApr","outputs":[{"internalType":"uint256","name":"baseApr","type":"uint256"},{"internalType":"uint256","name":"extraApr","type":"uint256"},{"internalType":"uint256","name":"totalApr","type":"uint256"}],"stateMutability":"view","type":"function"}]
Contract Creation Code
6080806040523461001657612dad908161001c8239f35b600080fdfe608080604052600436101561001357600080fd5b60003560e01c9081630237893214611b8a575080630de1605314611b055780635564838e14611a4157806370b320b714611a1e5780639d1607d9146118b55780639fbc422514610d7a578063a66f4dd6146102c3578063abd224ac146102a4578063f461407f146100c65763fe4ee9b91461008d57600080fd5b346100c15760403660031901126100c15760206100b96100ab611ba5565b6100b3611bbb565b90612abc565b604051908152f35b600080fd5b346100c15760803660031901126100c1576100df611ba5565b6100e7611bbb565b906100f0611bd1565b916101026100fc611be7565b93611d69565b926001600160a01b038093169060405193631b053f9960e11b85526020938486600481875afa95861561023857600096610272575b509060248592826040519586948593635677d7d760e11b8552166004840152165afa908115610238578390600092610244575b50600491926040519283809263016a7c9d60e21b82525afa92831561023857600093610208575b505061016d928381029381850414901517156101f2576101b092612a04565b906064820291808304606414901517156101f2576101ee6101d18383611ea9565b604051938493846040919493926060820195825260208201520152565b0390f35b634e487b7160e01b600052601160045260246000fd5b9080929350813d8311610231575b6102208183611cfe565b810103126100c15751908480610191565b503d610216565b6040513d6000823e3d90fd5b809250813d831161026b575b61025a8183611cfe565b810103126100c1575182600461016a565b503d610250565b919095508482813d831161029d575b61028b8183611cfe565b810103126100c1579051946024610137565b503d610281565b346100c15760003660031901126100c15760206040516301e133808152f35b346100c1576102d136611c62565b909597969194939260405190635677d7d760e11b82526001600160a01b038b1660048301526020826024816001600160a01b038c165afa91821561023857600092610d46575b5060405190635f2bcdbd60e01b82526020826004816001600160a01b0389165afa91821561023857600092610d11575b5060206001600160a01b0391600460405180948193635f2bcdbd60e01b8352165afa918215610238578391600093610cd7575b5061038d9261038891611ea9565b6127d1565b90604051907f7209f19a0000000000000000000000000000000000000000000000000000000082526020826004816001600160a01b038c165afa801561023857600090610ca3575b6103df92506127d1565b90816034810204603414821517156101f25760346103fd9202612903565b9860405191635677d7d760e11b835273ae7ab96520de3a18e5e111b5eaab095312d7fe8460048401526020836024816001600160a01b038c165afa92831561023857600093610c6f575b50604051630ecce30160e31b81526020816004816001600160a01b038e165afa90811561023857600091610c3d575b5060028110610c3557806001198101116101f2576040516331ad879360e21b8152600119820160048201526040816024816001600160a01b038d165afa90811561023857600091610c14575b50905b806000198101116101f257604080516331ad879360e21b8152600019830160048201529190826024816001600160a01b038e165afa91821561023857600092610bde575b5060041115610bce57505060206001600160a01b039160046040518094819363e60a5c4760e01b8352165afa90811561023857600091610b9c575b50808060021b04600414811517156101f257606460349160021b0404915b6001600160a01b0360405192635677d7d760e11b84521660048301526020826024816001600160a01b038c165afa91821561023857600092610b66575b506105aa91926127d1565b966040516330023a4d60e01b81526020816004816001600160a01b038b165afa90811561023857600091610b33575b5060206105ee6001600160a01b03928b61289b565b9260046040518094819363c89785a360e01b8352165afa8015610238578390600090610afd575b61061f92506127d1565b816034810204603414821517156101f257603461063c9202612903565b5060405190632a25ad1f60e21b82526020826004816001600160a01b038a165afa91821561023857600092610ac8575b5060206106816001600160a01b03938a61289b565b9360046040518095819363c89785a360e01b8352165afa801561023857600090610a94575b6106b092506127d1565b6034820291808304603414901517156101f2576106cc91612903565b9560405163559ab38760e01b81526020816004816001600160a01b0389165afa90811561023857600091610a61575b5060206107106001600160a01b03928961289b565b926004604051809481936318160ddd60e01b8352165afa90811561023857600091610a2f575b506034820291808304603414901517156101f25761075391612903565b5060405163051ed6a360e41b81526020816004816001600160a01b0387165afa8015610238576001600160a01b03869161079693600091610a16575b5016612abc565b6040519063c8e317cf60e01b82526020826004816001600160a01b0389165afa918215610238576000926109e1575b5060206107da6001600160a01b03938961289b565b946004604051809581936318160ddd60e01b8352165afa8015610238576000906109ad575b61080992506127d1565b916034810290808204603414901517156101f2576001600160a01b039261082f91612903565b50169260405163051ed6a360e41b8152602081600481885afa8015610238576001600160a01b03948561086d92602094600091610980575016612abc565b92600460405180968193630f5eeee760e11b8352165afa9283156102385760009361094a575b506108a260049360209261289b565b93604051938480926318160ddd60e01b82525afa801561023857600090610916575b6108ce92506127d1565b6034820291808304603414901517156101f2576108ea91612903565b506101ee6108f88284611ea9565b91604051938493846040919493926060820195825260208201520152565b506020823d602011610942575b8161093060209383611cfe565b810103126100c1576108ce91516108c4565b3d9150610923565b92506020833d602011610978575b8161096560209383611cfe565b810103126100c1579151916108a2610893565b3d9150610958565b6109a09150853d87116109a6575b6109988183611cfe565b810190612a9d565b8b61078f565b503d61098e565b506020823d6020116109d9575b816109c760209383611cfe565b810103126100c15761080991516107ff565b3d91506109ba565b91506020823d602011610a0e575b816109fc60209383611cfe565b810103126100c15790519060206107c5565b3d91506109ef565b6109a0915060203d6020116109a6576109988183611cfe565b90506020813d602011610a59575b81610a4a60209383611cfe565b810103126100c1575189610736565b3d9150610a3d565b90506020813d602011610a8c575b81610a7c60209383611cfe565b810103126100c1575160206106fb565b3d9150610a6f565b506020823d602011610ac0575b81610aae60209383611cfe565b810103126100c1576106b091516106a6565b3d9150610aa1565b91506020823d602011610af5575b81610ae360209383611cfe565b810103126100c157905190602061066c565b3d9150610ad6565b50506020813d602011610b2b575b81610b1860209383611cfe565b810103126100c1578261061f9151610615565b3d9150610b0b565b90506020813d602011610b5e575b81610b4e60209383611cfe565b810103126100c1575160206105d9565b3d9150610b41565b91506020823d602011610b94575b81610b8160209383611cfe565b810103126100c1576105aa91519161059f565b3d9150610b74565b90506020813d602011610bc6575b81610bb760209383611cfe565b810103126100c157518b610544565b3d9150610baa565b610bd89250612a7a565b91610562565b6004919250610c049060403d604011610c0d575b610bfc8183611cfe565b810190612a87565b90509190610509565b503d610bf2565b610c2d915060403d604011610c0d57610bfc8183611cfe565b90508d6104c2565b6000906104c5565b90506020813d602011610c67575b81610c5860209383611cfe565b810103126100c157518c610476565b3d9150610c4b565b9092506020813d602011610c9b575b81610c8b60209383611cfe565b810103126100c15751918b610447565b3d9150610c7e565b506020823d602011610ccf575b81610cbd60209383611cfe565b810103126100c1576103df91516103d5565b3d9150610cb0565b925090506020823d602011610d09575b81610cf460209383611cfe565b810103126100c157905190829061038861037a565b3d9150610ce7565b9091506020813d602011610d3e575b81610d2d60209383611cfe565b810103126100c15751906020610347565b3d9150610d20565b9091506020813d602011610d72575b81610d6260209383611cfe565b810103126100c15751908b610317565b3d9150610d55565b346100c157610d8836611c62565b9093959691925060409897985190635677d7d760e11b825273ae7ab96520de3a18e5e111b5eaab095312d7fe8460048301526020826024816001600160a01b0389165afa91821561023857600092611881575b50604051630ecce30160e31b81526020816004816001600160a01b038f165afa9081156102385760009161184f575b506002811061184757806001198101116101f2576040516331ad879360e21b8152600119820160048201526040816024816001600160a01b038a165afa90811561023857600091611826575b50905b806000198101116101f257604051906331ad879360e21b8252600019810160048301526040826024816001600160a01b038b165afa91821561023857600092611800575b50600411156117f157505060206001600160a01b039160046040518094819363e60a5c4760e01b8352165afa908115610238576000916117bf575b50808060021b04600414811517156101f257606460349160021b04045b6001600160a01b0360405199635677d7d760e11b8b521660048a01526020896024816001600160a01b0389165afa98891561023857600099611787575b5090610f3d916127d1565b976040516330023a4d60e01b81526020816004816001600160a01b0388165afa90811561023857600091611754575b506020610f816001600160a01b03928c61289b565b9260046040518094819363c89785a360e01b8352165afa801561023857899060009061171e575b610fb292506127d1565b816034810204603414821517156101f2576034610fcf9202612903565b9660405190632a25ad1f60e21b82526020826004816001600160a01b0388165afa918215610238576000926116e9575b5060206110146001600160a01b03938c61289b565b9360046040518095819363c89785a360e01b8352165afa8015610238576000906116b5575b61104392506127d1565b6034820291808304603414901517156101f25761105f91612903565b9460405163559ab38760e01b81526020816004816001600160a01b0387165afa90811561023857600091611682575b5060206110a36001600160a01b03928b61289b565b926004604051809481936318160ddd60e01b8352165afa90811561023857600091611650575b50816034810204603414821517156101f25760346110e79202612903565b9260405163051ed6a360e41b81526020816004816001600160a01b0386165afa90811561023857600091611631575b5060405190630240bc6b60e21b82526060826004816001600160a01b0385165afa8015610238576000926000916115d9575b5060405192630dfe168160e01b84526020846004816001600160a01b0387165afa938415610238576000946115b8575b506001600160a01b0360405194635677d7d760e11b86521660048501526020846024816001600160a01b038b165afa93841561023857600094611584575b506040519363d21220a760e01b85526020856004816001600160a01b0388165afa94851561023857600095611563575b506001600160a01b0360405195635677d7d760e11b87521660048601526020856024816001600160a01b038c165afa9485156102385760009561152b575b509161125a6001600160a01b0395611260936112536020966dffffffffffffffffffffffffffff809316612990565b9316612990565b90611ea9565b916004604051809581936318160ddd60e01b8352165afa908115610238576000916114f5575b6112909250612903565b6040519063c8e317cf60e01b82526020826004816001600160a01b0388165afa918215610238576000926114c0575b5060206112d46001600160a01b03938c61289b565b936004604051809581936318160ddd60e01b8352165afa80156102385760009061148c575b61130392506127d1565b906034810290808204603414901517156101f2576001600160a01b039161132991612903565b94169660405163051ed6a360e41b81526020816004818c5afa8015610238576001600160a01b0394856113679260209460009161146f575016612abc565b92600460405180968193630f5eeee760e11b8352165afa92831561023857600093611439575b5061139c60049360209261289b565b97604051938480926318160ddd60e01b82525afa801561023857600090611405575b6113c892506127d1565b946034810290808204603414901517156101f25760a0956113e891612903565b926040519485526020850152604084015260608301526080820152f35b506020823d602011611431575b8161141f60209383611cfe565b810103126100c1576113c891516113be565b3d9150611412565b92506020833d602011611467575b8161145460209383611cfe565b810103126100c15791519161139c61138d565b3d9150611447565b6114869150853d87116109a6576109988183611cfe565b8d61078f565b506020823d6020116114b8575b816114a660209383611cfe565b810103126100c15761130391516112f9565b3d9150611499565b91506020823d6020116114ed575b816114db60209383611cfe565b810103126100c15790519060206112bf565b3d91506114ce565b90506020823d602011611523575b8161151060209383611cfe565b810103126100c157611290915190611286565b3d9150611503565b9450916020853d60201161155b575b8161154760209383611cfe565b810103126100c1579351939161125a611224565b3d915061153a565b61157d91955060203d6020116109a6576109988183611cfe565b938d6111e6565b9093506020813d6020116115b0575b816115a060209383611cfe565b810103126100c15751928c6111b6565b3d9150611593565b6115d291945060203d6020116109a6576109988183611cfe565b928c611178565b9250506060823d8211611629575b816115f460609383611cfe565b810103126100c15761160582612d5c565b604061161360208501612d5c565b93015163ffffffff8116036100c157918b611148565b3d91506115e7565b61164a915060203d6020116109a6576109988183611cfe565b89611116565b90506020813d60201161167a575b8161166b60209383611cfe565b810103126100c15751896110c9565b3d915061165e565b90506020813d6020116116ad575b8161169d60209383611cfe565b810103126100c15751602061108e565b3d9150611690565b506020823d6020116116e1575b816116cf60209383611cfe565b810103126100c1576110439151611039565b3d91506116c2565b91506020823d602011611716575b8161170460209383611cfe565b810103126100c1579051906020610fff565b3d91506116f7565b50506020813d60201161174c575b8161173960209383611cfe565b810103126100c15788610fb29151610fa8565b3d915061172c565b90506020813d60201161177f575b8161176f60209383611cfe565b810103126100c157516020610f6c565b3d9150611762565b919098506020823d6020116117b7575b816117a460209383611cfe565b810103126100c157905197610f3d610f32565b3d9150611797565b90506020813d6020116117e9575b816117da60209383611cfe565b810103126100c157518a610ed8565b3d91506117cd565b6117fb9250612a7a565b610ef5565b600491925061181d9060403d604011610c0d57610bfc8183611cfe565b90509190610e9d565b61183f915060403d604011610c0d57610bfc8183611cfe565b90508c610e56565b600090610e59565b90506020813d602011611879575b8161186a60209383611cfe565b810103126100c157518b610e0a565b3d915061185d565b9091506020813d6020116118ad575b8161189d60209383611cfe565b810103126100c15751908a610ddb565b3d9150611890565b346100c1576118d16118c636611bfd565b918596938196611eb6565b5050505095925050506001600160a01b03809216604051631b053f9960e11b81526020938482600481865afa9384156102385785926000956119eb575b50602490826040519889948593635677d7d760e11b8552166004840152165afa938415610238576000946119bc575b50826004916040519283809263016a7c9d60e21b82525afa9283156102385760009361198c575b505061016d908181029181830414901517156101f2576101b09261198791611d36565b611d49565b9080929350813d83116119b5575b6119a48183611cfe565b810103126100c15751908480611964565b503d61199a565b9093508281813d83116119e4575b6119d48183611cfe565b810103126100c15751928261193d565b503d6119ca565b8381949296503d8311611a17575b611a038183611cfe565b810103126100c1576024859251949061190e565b503d6119f9565b346100c15760203660031901126100c15760206100b9611a3c611ba5565b611d69565b346100c157611a526118c636611bfd565b9996505050505050506001600160a01b03809216604051631b053f9960e11b81526020938482600481865afa9384156102385785926000956119eb5750602490826040519889948593635677d7d760e11b8552166004840152165afa938415610238576000946119bc5750826004916040519283809263016a7c9d60e21b82525afa9283156102385760009361198c57505061016d908181029181830414901517156101f2576101b09261198791611d36565b346100c15760a03660031901126100c157611b1e611ba5565b611b26611bbb565b90611b2f611bd1565b611b37611be7565b608435916001600160a01b03831683036100c15761010094611b5894611eb6565b95604095919594929451978852602088015260408701526060860152608085015260a084015260c083015260e0820152f35b346100c15760003660031901126100c1578061016d60209252f35b600435906001600160a01b03821682036100c157565b602435906001600160a01b03821682036100c157565b604435906001600160a01b03821682036100c157565b606435906001600160a01b03821682036100c157565b60c09060031901126100c1576001600160a01b039060043582811681036100c1579160243581811681036100c1579160443582811681036100c1579160643581811681036100c1579160843582811681036100c1579160a43590811681036100c15790565b6101409060031901126100c1576001600160a01b039060043582811681036100c1579160243581811681036100c1579160443582811681036100c1579160643581811681036100c1579160843582811681036100c1579160a43581811681036100c1579160c43582811681036100c1579160e43581811681036100c157916101043582811681036100c157916101243590811681036100c15790565b90601f8019910116810190811067ffffffffffffffff821117611d2057604052565b634e487b7160e01b600052604160045260246000fd5b818102929181159184041417156101f257565b8115611d53570490565b634e487b7160e01b600052601260045260246000fd5b6001600160a01b0316906040517fd11644000000000000000000000000000000000000000000000000000000000081526020928382600481845afa91821561023857600092611e33575b506301e13380918281029281840414901517156101f25783600491604051928380926318160ddd60e01b82525afa93841561023857600094611e00575b505091611dfd9192611d49565b90565b81813d8311611e2c575b611e148183611cfe565b81010312611e2857519250611dfd38611df0565b8380fd5b503d611e0a565b90918482813d8311611e5e575b611e4a8183611cfe565b81010312611e5b5750519038611db3565b80fd5b503d611e40565b908160609103126100c15760405190606082019082821067ffffffffffffffff831117611d2057604091825280518352602081015160208401520151604082015290565b919082018092116101f257565b91929094604051630df9d4bb60e01b81526001600160a01b038316600482015273ae7ab96520de3a18e5e111b5eaab095312d7fe8460248201526060816044816001600160a01b038b165afa8015610238576040916000916127b2575b50015194604051630df9d4bb60e01b81526001600160a01b038416600482015273cd5fe23c85820f7b72d0926fc9b05b43e359b7ee60248201526060816044816001600160a01b038c165afa801561023857604091600091612793575b500151604051630df9d4bb60e01b81526001600160a01b03858116600483015286166024820152909190606081806044810103816001600160a01b038d165afa801561023857604091600091612774575b5001519660405198635677d7d760e11b8a5273ae7ab96520de3a18e5e111b5eaab095312d7fe8460048b015260208a6024816001600160a01b038c165afa95861561023857600096612739575b88999a5060206001600160a01b0398996024604051809b8193635677d7d760e11b835273cd5fe23c85820f7b72d0926fc9b05b43e359b7ee6004840152165afa97881561023857600098612705575b5060405199635677d7d760e11b8b526001600160a01b038a1660048c015260208b6024816001600160a01b0385165afa9a8b156102385760009b6126d1575b506120b16120aa8284612abc565b9186612abc565b93806301e133808102046301e1338014811517156101f257886301e133806120d99202611d36565b60405163dd2a8b1160e01b81526001600160a01b03841660048201526020816024816001600160a01b0389165afa801561023857839160009161269a575b5061212c929161212691611d36565b90611d49565b95866064810204606414871517156101f257806301e133808102046301e1338014811517156101f257896301e133806121659202611d36565b60405163dd2a8b1160e01b81526001600160a01b03841660048201526020816024816001600160a01b0389165afa8015610238578391600091612663575b506121b2929161212691611d36565b9b8c60648102046064148d1517156101f257806301e133808102046301e1338014811517156101f2578b6301e133806121eb9202611d36565b916001600160a01b036040519163dd2a8b1160e01b83521660048201526020816024816001600160a01b0388165afa9081156102385760009161262f575b50612238929161212691611d36565b988960648102046064148a1517156101f2576122668b6122616064808e02920260648902611ea9565b611ea9565b98604051630df9d4bb60e01b81526001600160a01b038616600482015273ae7ab96520de3a18e5e111b5eaab095312d7fe8460248201526060816044816001600160a01b0388165afa801561023857604091600091612610575b500151604051630df9d4bb60e01b81526001600160a01b038716600482015273cd5fe23c85820f7b72d0926fc9b05b43e359b7ee60248201526060816044816001600160a01b0389165afa8015610238576040606091612357936000916125f3575b500151604051630df9d4bb60e01b81526001600160a01b038a8116600483015290951660248601529391829081906044820190565b03816001600160a01b0389165afa8015610238576040916000916125c4575b500151986301e133808202918083046301e1338014901517156101f25761239c91611d36565b60405163dd2a8b1160e01b81526001600160a01b03871660048201526020816024816001600160a01b0389165afa801561023857869160009161258d575b506123e9929161212691611d36565b988960648102046064148a1517156101f2576301e133808202918083046301e1338014901517156101f25761241d91611d36565b60405163dd2a8b1160e01b81526001600160a01b03861660048201526020816024816001600160a01b0388165afa8015610238578591600091612556575b5061246a929161212691611d36565b96876064810204606414881517156101f2576301e133808102908082046301e1338014901517156101f25760246124ab6020936001600160a01b0393611d36565b9582604051958694859363dd2a8b1160e01b8552166004840152165afa90811561023857600091612522575b506124e6929161212691611d36565b916064830292808404606414901517156101f257606480808080612512886122618b84028d8502611ea9565b96029b0299029796029402929190565b906020823d60201161254e575b8161253c60209383611cfe565b81010312611e5b5750516124e66124d7565b3d915061252f565b91506020823d602011612585575b8161257160209383611cfe565b81010312611e5b575051849061246a61245b565b3d9150612564565b91506020823d6020116125bc575b816125a860209383611cfe565b81010312611e5b57505185906123e96123da565b3d915061259b565b6125e6915060603d6060116125ec575b6125de8183611cfe565b810190611e65565b38612376565b503d6125d4565b61260a9150833d85116125ec576125de8183611cfe565b38612322565b612629915060603d6060116125ec576125de8183611cfe565b386122c0565b906020823d60201161265b575b8161264960209383611cfe565b81010312611e5b575051612238612229565b3d915061263c565b91506020823d602011612692575b8161267e60209383611cfe565b81010312611e5b57505182906121b26121a3565b3d9150612671565b91506020823d6020116126c9575b816126b560209383611cfe565b81010312611e5b575051829061212c612117565b3d91506126a8565b909a6020823d6020116126fd575b816126ec60209383611cfe565b81010312611e5b575051993861209c565b3d91506126df565b90976020823d602011612731575b8161272060209383611cfe565b81010312611e5b575051963861205d565b3d9150612713565b95996020813d60201161276c575b8161275460209383611cfe565b81010312612768575198995089989561200e565b8a80fd5b3d9150612747565b61278d915060603d6060116125ec576125de8183611cfe565b38611fc1565b6127ac915060603d6060116125ec576125de8183611cfe565b38611f70565b6127cb915060603d6060116125ec576125de8183611cfe565b38611f13565b906000198183098183029182808310920391808303921461288e576305f5e1009082821115612830577facbe0e98f503f8881186e60dbb7f727bf36b7213ee9f5a78c767074b22e90e21940990828211900360f81b910360081c170290565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f4d6174683a206d756c446976206f766572666c6f7700000000000000000000006044820152fd5b50506305f5e10091500490565b90600019818309818302918280831092039180830392146128f8576127109082821115612830577fbc01a36e2eb1c432ca57a786c226809d495182a9930be0ded288ce703afb7e91940990828211900360fc1b910360041c170290565b505061271091500490565b90670de0b6b3a76400009060001982840992828102928380861095039480860395146129835784831115612830578291096001821901821680920460028082600302188083028203028083028203028083028203028083028203028083028203028092029003029360018380600003040190848311900302920304170290565b505090611dfd9250611d49565b90600019818309818302918280831092039180830392146129f357670de0b6b3a76400009082821115612830577faccb18165bd6fe31ae1cf318dc5b51eee0e1ba569b88cd74c1773b91fac10669940990828211900360ee1b910360121c170290565b5050670de0b6b3a764000091500490565b9160001982840992828102928380861095039480860395146129835784831115612830578291096001821901821680920460028082600302188083028203028083028203028083028203028083028203028083028203028092029003029360018380600003040190848311900302920304170290565b919082039182116101f257565b91908260409103126100c1576020825192015190565b908160209103126100c157516001600160a01b03811681036100c15790565b91906001600160a01b0380931690604093845190630240bc6b60e21b82526004926060838581885afa908115612d51576000938492612cf6575b508216875193630dfe168160e01b8552602094858188818b5afa908115612ceb57600091612cce575b5089519285635677d7d760e11b9283865216888501528684602481845afa938415612cc357600094612c94575b508a519163d21220a760e01b835287838a818d5afa908115612c89578893600092612c69575b50602491928d5198899485938452168b8301525afa918215612c5e578594600093612c26575b509161125a91611253612bbd95946dffffffffffffffffffffffffffff809316612990565b938651938480926318160ddd60e01b82525afa948515612c1c5750600094612bed575b505091611dfd9192612903565b81813d8311612c15575b612c018183611cfe565b81010312611e2857519250611dfd38612be0565b503d612bf7565b513d6000823e3d90fd5b919290939482813d8311612c57575b612c3f8183611cfe565b81010312611e5b57505184939290919061125a612b98565b503d612c35565b89513d6000823e3d90fd5b60249250612c8390853d87116109a6576109988183611cfe565b91612b72565b8c513d6000823e3d90fd5b90938782813d8311612cbc575b612cab8183611cfe565b81010312611e5b5750519238612b4c565b503d612ca1565b8b513d6000823e3d90fd5b612ce59150863d88116109a6576109988183611cfe565b38612b1f565b8a513d6000823e3d90fd5b939091506060843d8211612d49575b81612d1260609383611cfe565b81010312611e5b57612d2384612d5c565b9088612d3160208701612d5c565b95015163ffffffff811603611e5b5750929082612af6565b3d9150612d05565b87513d6000823e3d90fd5b51906dffffffffffffffffffffffffffff821682036100c15756fea2646970667358221220b4cf5c89cf4cb5bef77c807ef2c929a151cd4f3f7e0c8f38966cc5b7178d8a4364736f6c63430008140033
Deployed Bytecode
0x608080604052600436101561001357600080fd5b60003560e01c9081630237893214611b8a575080630de1605314611b055780635564838e14611a4157806370b320b714611a1e5780639d1607d9146118b55780639fbc422514610d7a578063a66f4dd6146102c3578063abd224ac146102a4578063f461407f146100c65763fe4ee9b91461008d57600080fd5b346100c15760403660031901126100c15760206100b96100ab611ba5565b6100b3611bbb565b90612abc565b604051908152f35b600080fd5b346100c15760803660031901126100c1576100df611ba5565b6100e7611bbb565b906100f0611bd1565b916101026100fc611be7565b93611d69565b926001600160a01b038093169060405193631b053f9960e11b85526020938486600481875afa95861561023857600096610272575b509060248592826040519586948593635677d7d760e11b8552166004840152165afa908115610238578390600092610244575b50600491926040519283809263016a7c9d60e21b82525afa92831561023857600093610208575b505061016d928381029381850414901517156101f2576101b092612a04565b906064820291808304606414901517156101f2576101ee6101d18383611ea9565b604051938493846040919493926060820195825260208201520152565b0390f35b634e487b7160e01b600052601160045260246000fd5b9080929350813d8311610231575b6102208183611cfe565b810103126100c15751908480610191565b503d610216565b6040513d6000823e3d90fd5b809250813d831161026b575b61025a8183611cfe565b810103126100c1575182600461016a565b503d610250565b919095508482813d831161029d575b61028b8183611cfe565b810103126100c1579051946024610137565b503d610281565b346100c15760003660031901126100c15760206040516301e133808152f35b346100c1576102d136611c62565b909597969194939260405190635677d7d760e11b82526001600160a01b038b1660048301526020826024816001600160a01b038c165afa91821561023857600092610d46575b5060405190635f2bcdbd60e01b82526020826004816001600160a01b0389165afa91821561023857600092610d11575b5060206001600160a01b0391600460405180948193635f2bcdbd60e01b8352165afa918215610238578391600093610cd7575b5061038d9261038891611ea9565b6127d1565b90604051907f7209f19a0000000000000000000000000000000000000000000000000000000082526020826004816001600160a01b038c165afa801561023857600090610ca3575b6103df92506127d1565b90816034810204603414821517156101f25760346103fd9202612903565b9860405191635677d7d760e11b835273ae7ab96520de3a18e5e111b5eaab095312d7fe8460048401526020836024816001600160a01b038c165afa92831561023857600093610c6f575b50604051630ecce30160e31b81526020816004816001600160a01b038e165afa90811561023857600091610c3d575b5060028110610c3557806001198101116101f2576040516331ad879360e21b8152600119820160048201526040816024816001600160a01b038d165afa90811561023857600091610c14575b50905b806000198101116101f257604080516331ad879360e21b8152600019830160048201529190826024816001600160a01b038e165afa91821561023857600092610bde575b5060041115610bce57505060206001600160a01b039160046040518094819363e60a5c4760e01b8352165afa90811561023857600091610b9c575b50808060021b04600414811517156101f257606460349160021b0404915b6001600160a01b0360405192635677d7d760e11b84521660048301526020826024816001600160a01b038c165afa91821561023857600092610b66575b506105aa91926127d1565b966040516330023a4d60e01b81526020816004816001600160a01b038b165afa90811561023857600091610b33575b5060206105ee6001600160a01b03928b61289b565b9260046040518094819363c89785a360e01b8352165afa8015610238578390600090610afd575b61061f92506127d1565b816034810204603414821517156101f257603461063c9202612903565b5060405190632a25ad1f60e21b82526020826004816001600160a01b038a165afa91821561023857600092610ac8575b5060206106816001600160a01b03938a61289b565b9360046040518095819363c89785a360e01b8352165afa801561023857600090610a94575b6106b092506127d1565b6034820291808304603414901517156101f2576106cc91612903565b9560405163559ab38760e01b81526020816004816001600160a01b0389165afa90811561023857600091610a61575b5060206107106001600160a01b03928961289b565b926004604051809481936318160ddd60e01b8352165afa90811561023857600091610a2f575b506034820291808304603414901517156101f25761075391612903565b5060405163051ed6a360e41b81526020816004816001600160a01b0387165afa8015610238576001600160a01b03869161079693600091610a16575b5016612abc565b6040519063c8e317cf60e01b82526020826004816001600160a01b0389165afa918215610238576000926109e1575b5060206107da6001600160a01b03938961289b565b946004604051809581936318160ddd60e01b8352165afa8015610238576000906109ad575b61080992506127d1565b916034810290808204603414901517156101f2576001600160a01b039261082f91612903565b50169260405163051ed6a360e41b8152602081600481885afa8015610238576001600160a01b03948561086d92602094600091610980575016612abc565b92600460405180968193630f5eeee760e11b8352165afa9283156102385760009361094a575b506108a260049360209261289b565b93604051938480926318160ddd60e01b82525afa801561023857600090610916575b6108ce92506127d1565b6034820291808304603414901517156101f2576108ea91612903565b506101ee6108f88284611ea9565b91604051938493846040919493926060820195825260208201520152565b506020823d602011610942575b8161093060209383611cfe565b810103126100c1576108ce91516108c4565b3d9150610923565b92506020833d602011610978575b8161096560209383611cfe565b810103126100c1579151916108a2610893565b3d9150610958565b6109a09150853d87116109a6575b6109988183611cfe565b810190612a9d565b8b61078f565b503d61098e565b506020823d6020116109d9575b816109c760209383611cfe565b810103126100c15761080991516107ff565b3d91506109ba565b91506020823d602011610a0e575b816109fc60209383611cfe565b810103126100c15790519060206107c5565b3d91506109ef565b6109a0915060203d6020116109a6576109988183611cfe565b90506020813d602011610a59575b81610a4a60209383611cfe565b810103126100c1575189610736565b3d9150610a3d565b90506020813d602011610a8c575b81610a7c60209383611cfe565b810103126100c1575160206106fb565b3d9150610a6f565b506020823d602011610ac0575b81610aae60209383611cfe565b810103126100c1576106b091516106a6565b3d9150610aa1565b91506020823d602011610af5575b81610ae360209383611cfe565b810103126100c157905190602061066c565b3d9150610ad6565b50506020813d602011610b2b575b81610b1860209383611cfe565b810103126100c1578261061f9151610615565b3d9150610b0b565b90506020813d602011610b5e575b81610b4e60209383611cfe565b810103126100c1575160206105d9565b3d9150610b41565b91506020823d602011610b94575b81610b8160209383611cfe565b810103126100c1576105aa91519161059f565b3d9150610b74565b90506020813d602011610bc6575b81610bb760209383611cfe565b810103126100c157518b610544565b3d9150610baa565b610bd89250612a7a565b91610562565b6004919250610c049060403d604011610c0d575b610bfc8183611cfe565b810190612a87565b90509190610509565b503d610bf2565b610c2d915060403d604011610c0d57610bfc8183611cfe565b90508d6104c2565b6000906104c5565b90506020813d602011610c67575b81610c5860209383611cfe565b810103126100c157518c610476565b3d9150610c4b565b9092506020813d602011610c9b575b81610c8b60209383611cfe565b810103126100c15751918b610447565b3d9150610c7e565b506020823d602011610ccf575b81610cbd60209383611cfe565b810103126100c1576103df91516103d5565b3d9150610cb0565b925090506020823d602011610d09575b81610cf460209383611cfe565b810103126100c157905190829061038861037a565b3d9150610ce7565b9091506020813d602011610d3e575b81610d2d60209383611cfe565b810103126100c15751906020610347565b3d9150610d20565b9091506020813d602011610d72575b81610d6260209383611cfe565b810103126100c15751908b610317565b3d9150610d55565b346100c157610d8836611c62565b9093959691925060409897985190635677d7d760e11b825273ae7ab96520de3a18e5e111b5eaab095312d7fe8460048301526020826024816001600160a01b0389165afa91821561023857600092611881575b50604051630ecce30160e31b81526020816004816001600160a01b038f165afa9081156102385760009161184f575b506002811061184757806001198101116101f2576040516331ad879360e21b8152600119820160048201526040816024816001600160a01b038a165afa90811561023857600091611826575b50905b806000198101116101f257604051906331ad879360e21b8252600019810160048301526040826024816001600160a01b038b165afa91821561023857600092611800575b50600411156117f157505060206001600160a01b039160046040518094819363e60a5c4760e01b8352165afa908115610238576000916117bf575b50808060021b04600414811517156101f257606460349160021b04045b6001600160a01b0360405199635677d7d760e11b8b521660048a01526020896024816001600160a01b0389165afa98891561023857600099611787575b5090610f3d916127d1565b976040516330023a4d60e01b81526020816004816001600160a01b0388165afa90811561023857600091611754575b506020610f816001600160a01b03928c61289b565b9260046040518094819363c89785a360e01b8352165afa801561023857899060009061171e575b610fb292506127d1565b816034810204603414821517156101f2576034610fcf9202612903565b9660405190632a25ad1f60e21b82526020826004816001600160a01b0388165afa918215610238576000926116e9575b5060206110146001600160a01b03938c61289b565b9360046040518095819363c89785a360e01b8352165afa8015610238576000906116b5575b61104392506127d1565b6034820291808304603414901517156101f25761105f91612903565b9460405163559ab38760e01b81526020816004816001600160a01b0387165afa90811561023857600091611682575b5060206110a36001600160a01b03928b61289b565b926004604051809481936318160ddd60e01b8352165afa90811561023857600091611650575b50816034810204603414821517156101f25760346110e79202612903565b9260405163051ed6a360e41b81526020816004816001600160a01b0386165afa90811561023857600091611631575b5060405190630240bc6b60e21b82526060826004816001600160a01b0385165afa8015610238576000926000916115d9575b5060405192630dfe168160e01b84526020846004816001600160a01b0387165afa938415610238576000946115b8575b506001600160a01b0360405194635677d7d760e11b86521660048501526020846024816001600160a01b038b165afa93841561023857600094611584575b506040519363d21220a760e01b85526020856004816001600160a01b0388165afa94851561023857600095611563575b506001600160a01b0360405195635677d7d760e11b87521660048601526020856024816001600160a01b038c165afa9485156102385760009561152b575b509161125a6001600160a01b0395611260936112536020966dffffffffffffffffffffffffffff809316612990565b9316612990565b90611ea9565b916004604051809581936318160ddd60e01b8352165afa908115610238576000916114f5575b6112909250612903565b6040519063c8e317cf60e01b82526020826004816001600160a01b0388165afa918215610238576000926114c0575b5060206112d46001600160a01b03938c61289b565b936004604051809581936318160ddd60e01b8352165afa80156102385760009061148c575b61130392506127d1565b906034810290808204603414901517156101f2576001600160a01b039161132991612903565b94169660405163051ed6a360e41b81526020816004818c5afa8015610238576001600160a01b0394856113679260209460009161146f575016612abc565b92600460405180968193630f5eeee760e11b8352165afa92831561023857600093611439575b5061139c60049360209261289b565b97604051938480926318160ddd60e01b82525afa801561023857600090611405575b6113c892506127d1565b946034810290808204603414901517156101f25760a0956113e891612903565b926040519485526020850152604084015260608301526080820152f35b506020823d602011611431575b8161141f60209383611cfe565b810103126100c1576113c891516113be565b3d9150611412565b92506020833d602011611467575b8161145460209383611cfe565b810103126100c15791519161139c61138d565b3d9150611447565b6114869150853d87116109a6576109988183611cfe565b8d61078f565b506020823d6020116114b8575b816114a660209383611cfe565b810103126100c15761130391516112f9565b3d9150611499565b91506020823d6020116114ed575b816114db60209383611cfe565b810103126100c15790519060206112bf565b3d91506114ce565b90506020823d602011611523575b8161151060209383611cfe565b810103126100c157611290915190611286565b3d9150611503565b9450916020853d60201161155b575b8161154760209383611cfe565b810103126100c1579351939161125a611224565b3d915061153a565b61157d91955060203d6020116109a6576109988183611cfe565b938d6111e6565b9093506020813d6020116115b0575b816115a060209383611cfe565b810103126100c15751928c6111b6565b3d9150611593565b6115d291945060203d6020116109a6576109988183611cfe565b928c611178565b9250506060823d8211611629575b816115f460609383611cfe565b810103126100c15761160582612d5c565b604061161360208501612d5c565b93015163ffffffff8116036100c157918b611148565b3d91506115e7565b61164a915060203d6020116109a6576109988183611cfe565b89611116565b90506020813d60201161167a575b8161166b60209383611cfe565b810103126100c15751896110c9565b3d915061165e565b90506020813d6020116116ad575b8161169d60209383611cfe565b810103126100c15751602061108e565b3d9150611690565b506020823d6020116116e1575b816116cf60209383611cfe565b810103126100c1576110439151611039565b3d91506116c2565b91506020823d602011611716575b8161170460209383611cfe565b810103126100c1579051906020610fff565b3d91506116f7565b50506020813d60201161174c575b8161173960209383611cfe565b810103126100c15788610fb29151610fa8565b3d915061172c565b90506020813d60201161177f575b8161176f60209383611cfe565b810103126100c157516020610f6c565b3d9150611762565b919098506020823d6020116117b7575b816117a460209383611cfe565b810103126100c157905197610f3d610f32565b3d9150611797565b90506020813d6020116117e9575b816117da60209383611cfe565b810103126100c157518a610ed8565b3d91506117cd565b6117fb9250612a7a565b610ef5565b600491925061181d9060403d604011610c0d57610bfc8183611cfe565b90509190610e9d565b61183f915060403d604011610c0d57610bfc8183611cfe565b90508c610e56565b600090610e59565b90506020813d602011611879575b8161186a60209383611cfe565b810103126100c157518b610e0a565b3d915061185d565b9091506020813d6020116118ad575b8161189d60209383611cfe565b810103126100c15751908a610ddb565b3d9150611890565b346100c1576118d16118c636611bfd565b918596938196611eb6565b5050505095925050506001600160a01b03809216604051631b053f9960e11b81526020938482600481865afa9384156102385785926000956119eb575b50602490826040519889948593635677d7d760e11b8552166004840152165afa938415610238576000946119bc575b50826004916040519283809263016a7c9d60e21b82525afa9283156102385760009361198c575b505061016d908181029181830414901517156101f2576101b09261198791611d36565b611d49565b9080929350813d83116119b5575b6119a48183611cfe565b810103126100c15751908480611964565b503d61199a565b9093508281813d83116119e4575b6119d48183611cfe565b810103126100c15751928261193d565b503d6119ca565b8381949296503d8311611a17575b611a038183611cfe565b810103126100c1576024859251949061190e565b503d6119f9565b346100c15760203660031901126100c15760206100b9611a3c611ba5565b611d69565b346100c157611a526118c636611bfd565b9996505050505050506001600160a01b03809216604051631b053f9960e11b81526020938482600481865afa9384156102385785926000956119eb5750602490826040519889948593635677d7d760e11b8552166004840152165afa938415610238576000946119bc5750826004916040519283809263016a7c9d60e21b82525afa9283156102385760009361198c57505061016d908181029181830414901517156101f2576101b09261198791611d36565b346100c15760a03660031901126100c157611b1e611ba5565b611b26611bbb565b90611b2f611bd1565b611b37611be7565b608435916001600160a01b03831683036100c15761010094611b5894611eb6565b95604095919594929451978852602088015260408701526060860152608085015260a084015260c083015260e0820152f35b346100c15760003660031901126100c1578061016d60209252f35b600435906001600160a01b03821682036100c157565b602435906001600160a01b03821682036100c157565b604435906001600160a01b03821682036100c157565b606435906001600160a01b03821682036100c157565b60c09060031901126100c1576001600160a01b039060043582811681036100c1579160243581811681036100c1579160443582811681036100c1579160643581811681036100c1579160843582811681036100c1579160a43590811681036100c15790565b6101409060031901126100c1576001600160a01b039060043582811681036100c1579160243581811681036100c1579160443582811681036100c1579160643581811681036100c1579160843582811681036100c1579160a43581811681036100c1579160c43582811681036100c1579160e43581811681036100c157916101043582811681036100c157916101243590811681036100c15790565b90601f8019910116810190811067ffffffffffffffff821117611d2057604052565b634e487b7160e01b600052604160045260246000fd5b818102929181159184041417156101f257565b8115611d53570490565b634e487b7160e01b600052601260045260246000fd5b6001600160a01b0316906040517fd11644000000000000000000000000000000000000000000000000000000000081526020928382600481845afa91821561023857600092611e33575b506301e13380918281029281840414901517156101f25783600491604051928380926318160ddd60e01b82525afa93841561023857600094611e00575b505091611dfd9192611d49565b90565b81813d8311611e2c575b611e148183611cfe565b81010312611e2857519250611dfd38611df0565b8380fd5b503d611e0a565b90918482813d8311611e5e575b611e4a8183611cfe565b81010312611e5b5750519038611db3565b80fd5b503d611e40565b908160609103126100c15760405190606082019082821067ffffffffffffffff831117611d2057604091825280518352602081015160208401520151604082015290565b919082018092116101f257565b91929094604051630df9d4bb60e01b81526001600160a01b038316600482015273ae7ab96520de3a18e5e111b5eaab095312d7fe8460248201526060816044816001600160a01b038b165afa8015610238576040916000916127b2575b50015194604051630df9d4bb60e01b81526001600160a01b038416600482015273cd5fe23c85820f7b72d0926fc9b05b43e359b7ee60248201526060816044816001600160a01b038c165afa801561023857604091600091612793575b500151604051630df9d4bb60e01b81526001600160a01b03858116600483015286166024820152909190606081806044810103816001600160a01b038d165afa801561023857604091600091612774575b5001519660405198635677d7d760e11b8a5273ae7ab96520de3a18e5e111b5eaab095312d7fe8460048b015260208a6024816001600160a01b038c165afa95861561023857600096612739575b88999a5060206001600160a01b0398996024604051809b8193635677d7d760e11b835273cd5fe23c85820f7b72d0926fc9b05b43e359b7ee6004840152165afa97881561023857600098612705575b5060405199635677d7d760e11b8b526001600160a01b038a1660048c015260208b6024816001600160a01b0385165afa9a8b156102385760009b6126d1575b506120b16120aa8284612abc565b9186612abc565b93806301e133808102046301e1338014811517156101f257886301e133806120d99202611d36565b60405163dd2a8b1160e01b81526001600160a01b03841660048201526020816024816001600160a01b0389165afa801561023857839160009161269a575b5061212c929161212691611d36565b90611d49565b95866064810204606414871517156101f257806301e133808102046301e1338014811517156101f257896301e133806121659202611d36565b60405163dd2a8b1160e01b81526001600160a01b03841660048201526020816024816001600160a01b0389165afa8015610238578391600091612663575b506121b2929161212691611d36565b9b8c60648102046064148d1517156101f257806301e133808102046301e1338014811517156101f2578b6301e133806121eb9202611d36565b916001600160a01b036040519163dd2a8b1160e01b83521660048201526020816024816001600160a01b0388165afa9081156102385760009161262f575b50612238929161212691611d36565b988960648102046064148a1517156101f2576122668b6122616064808e02920260648902611ea9565b611ea9565b98604051630df9d4bb60e01b81526001600160a01b038616600482015273ae7ab96520de3a18e5e111b5eaab095312d7fe8460248201526060816044816001600160a01b0388165afa801561023857604091600091612610575b500151604051630df9d4bb60e01b81526001600160a01b038716600482015273cd5fe23c85820f7b72d0926fc9b05b43e359b7ee60248201526060816044816001600160a01b0389165afa8015610238576040606091612357936000916125f3575b500151604051630df9d4bb60e01b81526001600160a01b038a8116600483015290951660248601529391829081906044820190565b03816001600160a01b0389165afa8015610238576040916000916125c4575b500151986301e133808202918083046301e1338014901517156101f25761239c91611d36565b60405163dd2a8b1160e01b81526001600160a01b03871660048201526020816024816001600160a01b0389165afa801561023857869160009161258d575b506123e9929161212691611d36565b988960648102046064148a1517156101f2576301e133808202918083046301e1338014901517156101f25761241d91611d36565b60405163dd2a8b1160e01b81526001600160a01b03861660048201526020816024816001600160a01b0388165afa8015610238578591600091612556575b5061246a929161212691611d36565b96876064810204606414881517156101f2576301e133808102908082046301e1338014901517156101f25760246124ab6020936001600160a01b0393611d36565b9582604051958694859363dd2a8b1160e01b8552166004840152165afa90811561023857600091612522575b506124e6929161212691611d36565b916064830292808404606414901517156101f257606480808080612512886122618b84028d8502611ea9565b96029b0299029796029402929190565b906020823d60201161254e575b8161253c60209383611cfe565b81010312611e5b5750516124e66124d7565b3d915061252f565b91506020823d602011612585575b8161257160209383611cfe565b81010312611e5b575051849061246a61245b565b3d9150612564565b91506020823d6020116125bc575b816125a860209383611cfe565b81010312611e5b57505185906123e96123da565b3d915061259b565b6125e6915060603d6060116125ec575b6125de8183611cfe565b810190611e65565b38612376565b503d6125d4565b61260a9150833d85116125ec576125de8183611cfe565b38612322565b612629915060603d6060116125ec576125de8183611cfe565b386122c0565b906020823d60201161265b575b8161264960209383611cfe565b81010312611e5b575051612238612229565b3d915061263c565b91506020823d602011612692575b8161267e60209383611cfe565b81010312611e5b57505182906121b26121a3565b3d9150612671565b91506020823d6020116126c9575b816126b560209383611cfe565b81010312611e5b575051829061212c612117565b3d91506126a8565b909a6020823d6020116126fd575b816126ec60209383611cfe565b81010312611e5b575051993861209c565b3d91506126df565b90976020823d602011612731575b8161272060209383611cfe565b81010312611e5b575051963861205d565b3d9150612713565b95996020813d60201161276c575b8161275460209383611cfe565b81010312612768575198995089989561200e565b8a80fd5b3d9150612747565b61278d915060603d6060116125ec576125de8183611cfe565b38611fc1565b6127ac915060603d6060116125ec576125de8183611cfe565b38611f70565b6127cb915060603d6060116125ec576125de8183611cfe565b38611f13565b906000198183098183029182808310920391808303921461288e576305f5e1009082821115612830577facbe0e98f503f8881186e60dbb7f727bf36b7213ee9f5a78c767074b22e90e21940990828211900360f81b910360081c170290565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f4d6174683a206d756c446976206f766572666c6f7700000000000000000000006044820152fd5b50506305f5e10091500490565b90600019818309818302918280831092039180830392146128f8576127109082821115612830577fbc01a36e2eb1c432ca57a786c226809d495182a9930be0ded288ce703afb7e91940990828211900360fc1b910360041c170290565b505061271091500490565b90670de0b6b3a76400009060001982840992828102928380861095039480860395146129835784831115612830578291096001821901821680920460028082600302188083028203028083028203028083028203028083028203028083028203028092029003029360018380600003040190848311900302920304170290565b505090611dfd9250611d49565b90600019818309818302918280831092039180830392146129f357670de0b6b3a76400009082821115612830577faccb18165bd6fe31ae1cf318dc5b51eee0e1ba569b88cd74c1773b91fac10669940990828211900360ee1b910360121c170290565b5050670de0b6b3a764000091500490565b9160001982840992828102928380861095039480860395146129835784831115612830578291096001821901821680920460028082600302188083028203028083028203028083028203028083028203028083028203028092029003029360018380600003040190848311900302920304170290565b919082039182116101f257565b91908260409103126100c1576020825192015190565b908160209103126100c157516001600160a01b03811681036100c15790565b91906001600160a01b0380931690604093845190630240bc6b60e21b82526004926060838581885afa908115612d51576000938492612cf6575b508216875193630dfe168160e01b8552602094858188818b5afa908115612ceb57600091612cce575b5089519285635677d7d760e11b9283865216888501528684602481845afa938415612cc357600094612c94575b508a519163d21220a760e01b835287838a818d5afa908115612c89578893600092612c69575b50602491928d5198899485938452168b8301525afa918215612c5e578594600093612c26575b509161125a91611253612bbd95946dffffffffffffffffffffffffffff809316612990565b938651938480926318160ddd60e01b82525afa948515612c1c5750600094612bed575b505091611dfd9192612903565b81813d8311612c15575b612c018183611cfe565b81010312611e2857519250611dfd38612be0565b503d612bf7565b513d6000823e3d90fd5b919290939482813d8311612c57575b612c3f8183611cfe565b81010312611e5b57505184939290919061125a612b98565b503d612c35565b89513d6000823e3d90fd5b60249250612c8390853d87116109a6576109988183611cfe565b91612b72565b8c513d6000823e3d90fd5b90938782813d8311612cbc575b612cab8183611cfe565b81010312611e5b5750519238612b4c565b503d612ca1565b8b513d6000823e3d90fd5b612ce59150863d88116109a6576109988183611cfe565b38612b1f565b8a513d6000823e3d90fd5b939091506060843d8211612d49575b81612d1260609383611cfe565b81010312611e5b57612d2384612d5c565b9088612d3160208701612d5c565b95015163ffffffff811603611e5b5750929082612af6565b3d9150612d05565b87513d6000823e3d90fd5b51906dffffffffffffffffffffffffffff821682036100c15756fea2646970667358221220b4cf5c89cf4cb5bef77c807ef2c929a151cd4f3f7e0c8f38966cc5b7178d8a4364736f6c63430008140033
Loading...
Loading
Loading...
Loading
Multichain Portfolio | 35 Chains
Chain | Token | Portfolio % | Price | Amount | Value |
---|
Loading...
Loading
Loading...
Loading
Loading...
Loading
A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.