diff --git a/.gitignore b/.gitignore index ae59a47..11e28df 100644 --- a/.gitignore +++ b/.gitignore @@ -112,4 +112,4 @@ typechain #Hardhat files cache artifacts -.openzeppelin +.openzeppelin \ No newline at end of file diff --git a/contracts/hyphen/LiquidityFarming.sol b/contracts/hyphen/LiquidityFarming.sol index 53bcd49..6a32689 100644 --- a/contracts/hyphen/LiquidityFarming.sol +++ b/contracts/hyphen/LiquidityFarming.sol @@ -99,6 +99,10 @@ contract HyphenLiquidityFarming is lpToken = _lpToken; } + function setTrustedForwarder(address _tf) external onlyOwner { + _setTrustedForwarder(_tf); + } + /// @notice Initialize the rewarder pool. /// @param _baseToken Base token to be used for the rewarder pool. /// @param _rewardToken Reward token to be used for the rewarder pool. @@ -183,6 +187,7 @@ contract HyphenLiquidityFarming is /// @notice Sets the sushi per second to be distributed. Can only be called by the owner. /// @param _rewardPerSecond The amount of Sushi to be distributed per second. function setRewardPerSecond(address _baseToken, uint256 _rewardPerSecond) external onlyOwner { + require(_rewardPerSecond <= 10**40, "ERR__REWARD_PER_SECOND_TOO_HIGH"); rewardRateLog[_baseToken].push(RewardsPerSecondEntry(_rewardPerSecond, block.timestamp)); emit LogRewardPerSecond(_baseToken, _rewardPerSecond); } @@ -245,17 +250,35 @@ contract HyphenLiquidityFarming is /// @param _nftId LP token nftId to withdraw. /// @param _to The receiver of `amount` withdraw benefit. function withdraw(uint256 _nftId, address payable _to) external whenNotPaused nonReentrant { - address msgSender = _msgSender(); - uint256 nftsStakedLength = nftIdsStaked[msgSender].length; + uint256 index = getStakedNftIndex(_msgSender(), _nftId); + _withdraw(_nftId, _to, index); + } + + function getStakedNftIndex(address _staker, uint256 _nftId) public view returns (uint256) { + uint256 nftsStakedLength = nftIdsStaked[_staker].length; uint256 index; - for (index = 0; index < nftsStakedLength; ++index) { - if (nftIdsStaked[msgSender][index] == _nftId) { - break; + unchecked { + for (index = 0; index < nftsStakedLength; ++index) { + if (nftIdsStaked[_staker][index] == _nftId) { + return index; + } } } + revert("ERR__NFT_NOT_STAKED"); + } + + function _withdraw( + uint256 _nftId, + address payable _to, + uint256 _index + ) private { + address msgSender = _msgSender(); + + require(nftIdsStaked[msgSender][_index] == _nftId, "ERR__NOT_OWNER"); + require(nftInfo[_nftId].staker == msgSender, "ERR__NOT_OWNER"); + require(nftInfo[_nftId].unpaidRewards == 0, "ERR__UNPAID_REWARDS_EXIST"); - require(index != nftsStakedLength, "ERR__NFT_NOT_STAKED"); - nftIdsStaked[msgSender][index] = nftIdsStaked[msgSender][nftIdsStaked[msgSender].length - 1]; + nftIdsStaked[msgSender][_index] = nftIdsStaked[msgSender][nftIdsStaked[msgSender].length - 1]; nftIdsStaked[msgSender].pop(); _sendRewardsForNft(_nftId, _to); @@ -270,6 +293,14 @@ contract HyphenLiquidityFarming is emit LogWithdraw(msgSender, baseToken, _nftId, _to); } + function withdrawV2( + uint256 _nftId, + address payable _to, + uint256 _index + ) external whenNotPaused nonReentrant { + _withdraw(_nftId, _to, _index); + } + /// @notice Extract all rewards without withdrawing LP tokens /// @param _nftId LP token nftId for which rewards are to be withdrawn /// @param _to The receiver of withdraw benefit. @@ -332,14 +363,12 @@ contract HyphenLiquidityFarming is /// @return pool Returns the pool that was updated. function updatePool(address _baseToken) public whenNotPaused returns (PoolInfo memory pool) { pool = poolInfo[_baseToken]; - if (block.timestamp > pool.lastRewardTime) { - if (totalSharesStaked[_baseToken] > 0) { - pool.accTokenPerShare = getUpdatedAccTokenPerShare(_baseToken); - } - pool.lastRewardTime = block.timestamp; - poolInfo[_baseToken] = pool; - emit LogUpdatePool(_baseToken, pool.lastRewardTime, totalSharesStaked[_baseToken], pool.accTokenPerShare); + if (totalSharesStaked[_baseToken] > 0) { + pool.accTokenPerShare = getUpdatedAccTokenPerShare(_baseToken); } + pool.lastRewardTime = block.timestamp; + poolInfo[_baseToken] = pool; + emit LogUpdatePool(_baseToken, pool.lastRewardTime, totalSharesStaked[_baseToken], pool.accTokenPerShare); } /// @notice View function to see the tokens staked by a given user. diff --git a/contracts/hyphen/LiquidityPool.sol b/contracts/hyphen/LiquidityPool.sol index d55722d..ab48a5f 100644 --- a/contracts/hyphen/LiquidityPool.sol +++ b/contracts/hyphen/LiquidityPool.sol @@ -84,9 +84,9 @@ contract LiquidityPool is string tag ); event GasFeeWithdraw(address indexed tokenAddress, address indexed owner, uint256 indexed amount); - event TrustedForwarderChanged(address indexed forwarderAddress); event LiquidityProvidersChanged(address indexed liquidityProvidersAddress); event TokenManagerChanged(address indexed tokenManagerAddress); + event BaseGasUpdated(uint256 indexed baseGas); event EthReceived(address, uint256); // MODIFIERS @@ -127,9 +127,7 @@ contract LiquidityPool is } function setTrustedForwarder(address trustedForwarder) external onlyOwner { - require(trustedForwarder != address(0), "TrustedForwarder can't be 0"); - _trustedForwarder = trustedForwarder; - emit TrustedForwarderChanged(trustedForwarder); + _setTrustedForwarder(trustedForwarder); } function setLiquidityProviders(address _liquidityProviders) external onlyOwner { @@ -146,6 +144,7 @@ contract LiquidityPool is function setBaseGas(uint128 gas) external onlyOwner { baseGas = gas; + emit BaseGasUpdated(baseGas); } function getExecutorManager() external view returns (address) { @@ -181,6 +180,8 @@ contract LiquidityPool is uint256 amount, string calldata tag ) public tokenChecks(tokenAddress) whenNotPaused nonReentrant { + require(toChainId != block.chainid, "To chain must be different than current chain"); + require(tokenAddress != NATIVE, "wrong function"); TokenConfig memory config = tokenManager.getDepositConfig(toChainId, tokenAddress); require(config.min <= amount && config.max >= amount, "Deposit amount not in Cap limit"); @@ -270,6 +271,7 @@ contract LiquidityPool is uint256 toChainId, string calldata tag ) external payable whenNotPaused nonReentrant { + require(toChainId != block.chainid, "To chain must be different than current chain"); require( tokenManager.getDepositConfig(toChainId, NATIVE).min <= msg.value && tokenManager.getDepositConfig(toChainId, NATIVE).max >= msg.value, @@ -369,28 +371,125 @@ contract LiquidityPool is return [amountToTransfer, lpFee, transferFeeAmount, gasFee]; } + function sendFundsToUserV2( + address tokenAddress, + uint256 amount, + address payable receiver, + bytes calldata depositHash, + uint256 nativeTokenPriceInTransferredToken, + uint256 fromChainId + ) external nonReentrant onlyExecutor whenNotPaused { + uint256 initialGas = gasleft(); + TokenConfig memory config = tokenManager.getTransferConfig(tokenAddress); + require(config.min <= amount && config.max >= amount, "Withdraw amount not in Cap limit"); + require(receiver != address(0), "Bad receiver address"); + + (bytes32 hashSendTransaction, bool status) = checkHashStatus(tokenAddress, amount, receiver, depositHash); + + require(!status, "Already Processed"); + processedHash[hashSendTransaction] = true; + + // uint256 amountToTransfer, uint256 lpFee, uint256 transferFeeAmount, uint256 gasFee + uint256[4] memory transferDetails = getAmountToTransferV2( + initialGas, + tokenAddress, + amount, + nativeTokenPriceInTransferredToken + ); + + liquidityProviders.decreaseCurrentLiquidity(tokenAddress, transferDetails[0]); + + if (tokenAddress == NATIVE) { + (bool success, ) = receiver.call{value: transferDetails[0]}(""); + require(success, "Native Transfer Failed"); + } else { + SafeERC20Upgradeable.safeTransfer(IERC20Upgradeable(tokenAddress), receiver, transferDetails[0]); + } + + emit AssetSent( + tokenAddress, + amount, + transferDetails[0], + receiver, + depositHash, + fromChainId, + transferDetails[1], + transferDetails[2], + transferDetails[3] + ); + } + + /** + * @dev Internal function to calculate amount of token that needs to be transfered afetr deducting all required fees. + * Fee to be deducted includes gas fee, lp fee and incentive pool amount if needed. + * @param initialGas Gas provided initially before any calculations began + * @param tokenAddress Token address for which calculation needs to be done + * @param amount Amount of token to be transfered before deducting the fee + * @param nativeTokenPriceInTransferredToken Price of native token in terms of the token being transferred (multiplied base div), used to calculate gas fee + * @return [ amountToTransfer, lpFee, transferFeeAmount, gasFee ] + */ + function getAmountToTransferV2( + uint256 initialGas, + address tokenAddress, + uint256 amount, + uint256 nativeTokenPriceInTransferredToken + ) internal returns (uint256[4] memory) { + TokenInfo memory tokenInfo = tokenManager.getTokensInfo(tokenAddress); + uint256 transferFeePerc = _getTransferFee(tokenAddress, amount, tokenInfo); + uint256 lpFee; + if (transferFeePerc > tokenInfo.equilibriumFee) { + // Here add some fee to incentive pool also + lpFee = (amount * tokenInfo.equilibriumFee) / BASE_DIVISOR; + unchecked { + incentivePool[tokenAddress] += (amount * (transferFeePerc - tokenInfo.equilibriumFee)) / BASE_DIVISOR; + } + } else { + lpFee = (amount * transferFeePerc) / BASE_DIVISOR; + } + uint256 transferFeeAmount = (amount * transferFeePerc) / BASE_DIVISOR; + + liquidityProviders.addLPFee(tokenAddress, lpFee); + + uint256 totalGasUsed = initialGas + tokenInfo.transferOverhead + baseGas - gasleft(); + uint256 gasFee = (totalGasUsed * nativeTokenPriceInTransferredToken * tx.gasprice) / BASE_DIVISOR; + + gasFeeAccumulatedByToken[tokenAddress] += gasFee; + gasFeeAccumulated[tokenAddress][_msgSender()] += gasFee; + uint256 amountToTransfer = amount - (transferFeeAmount + gasFee); + return [amountToTransfer, lpFee, transferFeeAmount, gasFee]; + } + function _getTransferFee( address tokenAddress, uint256 amount, TokenInfo memory tokenInfo - ) private view returns (uint256 fee) { + ) private view returns (uint256) { uint256 currentLiquidity = getCurrentLiquidity(tokenAddress); uint256 providedLiquidity = liquidityProviders.getSuppliedLiquidityByToken(tokenAddress); uint256 resultingLiquidity = currentLiquidity - amount; + // We return a constant value in excess state + if (resultingLiquidity > providedLiquidity) { + return tokenManager.excessStateTransferFeePerc(tokenAddress); + } + // Fee is represented in basis points * 10 for better accuracy - uint256 numerator = providedLiquidity * tokenInfo.equilibriumFee * tokenInfo.maxFee; // F(max) * F(e) * L(e) + uint256 numerator = providedLiquidity * providedLiquidity * tokenInfo.equilibriumFee * tokenInfo.maxFee; // F(max) * F(e) * L(e) ^ 2 uint256 denominator = tokenInfo.equilibriumFee * + providedLiquidity * providedLiquidity + (tokenInfo.maxFee - tokenInfo.equilibriumFee) * - resultingLiquidity; // F(e) * L(e) + (F(max) - F(e)) * L(r) + resultingLiquidity * + resultingLiquidity; // F(e) * L(e) ^ 2 + (F(max) - F(e)) * L(r) ^ 2 + uint256 fee; if (denominator == 0) { fee = 0; } else { fee = numerator / denominator; } + return fee; } function getTransferFee(address tokenAddress, uint256 amount) external view returns (uint256) { diff --git a/contracts/hyphen/LiquidityProviders.sol b/contracts/hyphen/LiquidityProviders.sol index 6b25ca7..f3bd927 100644 --- a/contracts/hyphen/LiquidityProviders.sol +++ b/contracts/hyphen/LiquidityProviders.sol @@ -1,14 +1,14 @@ -// $$\ $$\ $$\ $$\ $$\ $$\ $$$$$$$\ $$\ $$\ -// $$ | \__| \__| $$ |\__| $$ | $$ __$$\ \__| $$ | -// $$ | $$\ $$$$$$\ $$\ $$\ $$\ $$$$$$$ |$$\ $$$$$$\ $$\ $$\ $$ | $$ | $$$$$$\ $$$$$$\ $$\ $$\ $$\ $$$$$$$ | $$$$$$\ $$$$$$\ $$$$$$$\ +// $$\ $$\ $$\ $$\ $$\ $$\ $$$$$$$\ $$\ $$\ +// $$ | \__| \__| $$ |\__| $$ | $$ __$$\ \__| $$ | +// $$ | $$\ $$$$$$\ $$\ $$\ $$\ $$$$$$$ |$$\ $$$$$$\ $$\ $$\ $$ | $$ | $$$$$$\ $$$$$$\ $$\ $$\ $$\ $$$$$$$ | $$$$$$\ $$$$$$\ $$$$$$$\ // $$ | $$ |$$ __$$\ $$ | $$ |$$ |$$ __$$ |$$ |\_$$ _| $$ | $$ | $$$$$$$ |$$ __$$\ $$ __$$\\$$\ $$ |$$ |$$ __$$ |$$ __$$\ $$ __$$\ $$ _____| -// $$ | $$ |$$ / $$ |$$ | $$ |$$ |$$ / $$ |$$ | $$ | $$ | $$ | $$ ____/ $$ | \__|$$ / $$ |\$$\$$ / $$ |$$ / $$ |$$$$$$$$ |$$ | \__|\$$$$$$\ -// $$ | $$ |$$ | $$ |$$ | $$ |$$ |$$ | $$ |$$ | $$ |$$\ $$ | $$ | $$ | $$ | $$ | $$ | \$$$ / $$ |$$ | $$ |$$ ____|$$ | \____$$\ +// $$ | $$ |$$ / $$ |$$ | $$ |$$ |$$ / $$ |$$ | $$ | $$ | $$ | $$ ____/ $$ | \__|$$ / $$ |\$$\$$ / $$ |$$ / $$ |$$$$$$$$ |$$ | \__|\$$$$$$\ +// $$ | $$ |$$ | $$ |$$ | $$ |$$ |$$ | $$ |$$ | $$ |$$\ $$ | $$ | $$ | $$ | $$ | $$ | \$$$ / $$ |$$ | $$ |$$ ____|$$ | \____$$\ // $$$$$$$$\ $$ |\$$$$$$$ |\$$$$$$ |$$ |\$$$$$$$ |$$ | \$$$$ |\$$$$$$$ | $$ | $$ | \$$$$$$ | \$ / $$ |\$$$$$$$ |\$$$$$$$\ $$ | $$$$$$$ | -// \________|\__| \____$$ | \______/ \__| \_______|\__| \____/ \____$$ | \__| \__| \______/ \_/ \__| \_______| \_______|\__| \_______/ -// $$ | $$\ $$ | -// $$ | \$$$$$$ | -// \__| \______/ +// \________|\__| \____$$ | \______/ \__| \_______|\__| \____/ \____$$ | \__| \__| \______/ \_/ \__| \_______| \_______|\__| \_______/ +// $$ | $$\ $$ | +// $$ | \$$$$$$ | +// \__| \______/ // SPDX-License-Identifier: MIT pragma solidity 0.8.0; @@ -167,6 +167,10 @@ contract LiquidityProviders is tokenManager = ITokenManager(_tokenManager); } + function setTrustedForwarder(address _tf) external onlyOwner { + _setTrustedForwarder(_tf); + } + /** * @dev To be called post initialization, used to set address of WhiteListPeriodManager Contract * @param _whiteListPeriodManager address of WhiteListPeriodManager @@ -189,8 +193,8 @@ contract LiquidityProviders is * @return Price of Base token in terms of LP Shares */ function getTokenPriceInLPShares(address _baseToken) public view returns (uint256) { - uint256 supply = totalSharesMinted[_baseToken]; - if (supply > 0) { + uint256 reserve = totalReserve[_baseToken]; + if (reserve > 0) { return totalSharesMinted[_baseToken] / totalReserve[_baseToken]; } return BASE_DIVISOR; @@ -259,6 +263,7 @@ contract LiquidityProviders is * record in the newly minted NFT */ function addNativeLiquidity() external payable nonReentrant tokenChecks(NATIVE) whenNotPaused { + require(address(liquidityPool) != address(0), "ERR__LIQUIDITY_POOL_NOT_SET"); (bool success, ) = address(liquidityPool).call{value: msg.value}(""); require(success, "ERR__NATIVE_TRANSFER_FAILED"); _addLiquidity(NATIVE, msg.value); @@ -296,7 +301,7 @@ contract LiquidityProviders is uint256 mintedSharesAmount; // Adding liquidity in the pool for the first time - if (totalReserve[token] == 0) { + if (totalReserve[token] == 0 || totalSharesMinted[token] == 0) { mintedSharesAmount = BASE_DIVISOR * _amount; } else { mintedSharesAmount = (_amount * totalSharesMinted[token]) / totalReserve[token]; @@ -344,6 +349,7 @@ contract LiquidityProviders is (address token, , ) = lpToken.tokenMetadata(_nftId); require(_isSupportedToken(NATIVE), "ERR__TOKEN_NOT_SUPPORTED"); require(token == NATIVE, "ERR__WRONG_FUNCTION"); + require(address(liquidityPool) != address(0), "ERR__LIQUIDITY_POOL_NOT_SET"); (bool success, ) = address(liquidityPool).call{value: msg.value}(""); require(success, "ERR__NATIVE_TRANSFER_FAILED"); _increaseLiquidity(_nftId, msg.value); @@ -365,7 +371,7 @@ contract LiquidityProviders is require(_amount != 0, "ERR__INVALID_AMOUNT"); require(nftSuppliedLiquidity >= _amount, "ERR__INSUFFICIENT_LIQUIDITY"); whiteListPeriodManager.beforeLiquidityRemoval(_msgSender(), _tokenAddress, _amount); - // Claculate how much shares represent input amount + // Calculate how much shares represent input amount uint256 lpSharesForInputAmount = _amount * getTokenPriceInLPShares(_tokenAddress); // Calculate rewards accumulated diff --git a/contracts/hyphen/interfaces/ITokenManager.sol b/contracts/hyphen/interfaces/ITokenManager.sol index 93c2c09..0bbd594 100644 --- a/contracts/hyphen/interfaces/ITokenManager.sol +++ b/contracts/hyphen/interfaces/ITokenManager.sol @@ -25,9 +25,13 @@ interface ITokenManager { TokenConfig memory config ); + function excessStateTransferFeePerc(address tokenAddress) external view returns (uint256); + function getTokensInfo(address tokenAddress) external view returns (TokenInfo memory); function getDepositConfig(uint256 toChainId, address tokenAddress) external view returns (TokenConfig memory); function getTransferConfig(address tokenAddress) external view returns (TokenConfig memory); + + function changeExcessStateFee(address _tokenAddress, uint256 _excessStateFeePer) external; } diff --git a/contracts/hyphen/metatx/ERC2771ContextUpgradeable.sol b/contracts/hyphen/metatx/ERC2771ContextUpgradeable.sol index e64afde..473b9e5 100644 --- a/contracts/hyphen/metatx/ERC2771ContextUpgradeable.sol +++ b/contracts/hyphen/metatx/ERC2771ContextUpgradeable.sol @@ -6,11 +6,13 @@ import "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; /** - * @dev Context variant with ERC2771 support. + * @dev Context variant with ERC2771 support. * Here _trustedForwarder is made internal instead of private * so it can be changed via Child contracts with a setter method. */ abstract contract ERC2771ContextUpgradeable is Initializable, ContextUpgradeable { + event TrustedForwarderChanged(address indexed _tf); + address internal _trustedForwarder; function __ERC2771Context_init(address trustedForwarder) internal initializer { @@ -44,5 +46,12 @@ abstract contract ERC2771ContextUpgradeable is Initializable, ContextUpgradeable return super._msgData(); } } + + function _setTrustedForwarder(address _tf) internal virtual { + require(_tf != address(0), "TrustedForwarder can't be 0"); + _trustedForwarder = _tf; + emit TrustedForwarderChanged(_tf); + } + uint256[49] private __gap; } diff --git a/contracts/hyphen/token/TokenManager.sol b/contracts/hyphen/token/TokenManager.sol index 469d0ee..d8378f5 100644 --- a/contracts/hyphen/token/TokenManager.sol +++ b/contracts/hyphen/token/TokenManager.sol @@ -13,15 +13,19 @@ pragma solidity 0.8.0; -import "@openzeppelin/contracts/access/Ownable.sol"; -import "@openzeppelin/contracts/security/Pausable.sol"; -import "../metatx/ERC2771Context.sol"; +import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import "../../security/Pausable.sol"; +import "../metatx/ERC2771ContextUpgradeable.sol"; import "../interfaces/ITokenManager.sol"; -contract TokenManager is ITokenManager, ERC2771Context, Ownable, Pausable { +contract TokenManager is ITokenManager, ERC2771ContextUpgradeable, OwnableUpgradeable, Pausable { mapping(address => TokenInfo) public override tokensInfo; + // Excess State Transfer Fee Percentage + mapping(address => uint256) public override excessStateTransferFeePerc; + event FeeChanged(address indexed tokenAddress, uint256 indexed equilibriumFee, uint256 indexed maxFee); + event ExcessStateTransferFeePercChanged(address indexed tokenAddress, uint256 indexed fee); modifier tokenChecks(address tokenAddress) { require(tokenAddress != address(0), "Token address cannot be 0"); @@ -40,8 +44,10 @@ contract TokenManager is ITokenManager, ERC2771Context, Ownable, Pausable { */ mapping(address => TokenConfig) public transferConfig; - constructor(address trustedForwarder) ERC2771Context(trustedForwarder) Ownable() Pausable() { - // Empty Constructor + function initialize(address trustedForwarder, address pauser) external initializer { + __ERC2771Context_init(trustedForwarder); + __Ownable_init(); + __Pausable_init(pauser); } function getEquilibriumFee(address tokenAddress) public view override returns (uint256) { @@ -59,11 +65,24 @@ contract TokenManager is ITokenManager, ERC2771Context, Ownable, Pausable { ) external override onlyOwner whenNotPaused { require(_equilibriumFee != 0, "Equilibrium Fee cannot be 0"); require(_maxFee != 0, "Max Fee cannot be 0"); + require(_equilibriumFee <= _maxFee && _maxFee <= 10000000000, "Max Fee cannot be greater than 100%"); tokensInfo[tokenAddress].equilibriumFee = _equilibriumFee; tokensInfo[tokenAddress].maxFee = _maxFee; emit FeeChanged(tokenAddress, tokensInfo[tokenAddress].equilibriumFee, tokensInfo[tokenAddress].maxFee); } + function changeExcessStateFee(address _tokenAddress, uint256 _excessStateFeePer) + external + override + onlyOwner + whenNotPaused + { + require(_tokenAddress != address(0), "Token address cannot be 0"); + require(_excessStateFeePer != 0, "Excess State Fee Percentage cannot be 0"); + excessStateTransferFeePerc[_tokenAddress] = _excessStateFeePer; + emit ExcessStateTransferFeePercChanged(_tokenAddress, _excessStateFeePer); + } + function setTokenTransferOverhead(address tokenAddress, uint256 gasOverhead) external tokenChecks(tokenAddress) @@ -153,19 +172,27 @@ contract TokenManager is ITokenManager, ERC2771Context, Ownable, Pausable { return transferConfig[tokenAddress]; } - function _msgSender() internal view virtual override(Context, ERC2771Context) returns (address sender) { - return ERC2771Context._msgSender(); - } - - function _msgData() internal view virtual override(Context, ERC2771Context) returns (bytes calldata) { - return ERC2771Context._msgData(); + function _msgSender() + internal + view + virtual + override(ContextUpgradeable, ERC2771ContextUpgradeable) + returns (address sender) + { + return ERC2771ContextUpgradeable._msgSender(); } - function pause() external onlyOwner { - _pause(); + function _msgData() + internal + view + virtual + override(ContextUpgradeable, ERC2771ContextUpgradeable) + returns (bytes calldata) + { + return ERC2771ContextUpgradeable._msgData(); } - function unpause() external onlyOwner { - _unpause(); + function setTrustedForwarder(address _tf) external onlyOwner { + _setTrustedForwarder(_tf); } } diff --git a/contracts/security/Pausable.sol b/contracts/security/Pausable.sol index b2be42c..a6eeb65 100644 --- a/contracts/security/Pausable.sol +++ b/contracts/security/Pausable.sol @@ -48,7 +48,7 @@ abstract contract Pausable is Initializable, PausableUpgradeable { * @dev Allows the current pauser to transfer control of the contract to a newPauser. * @param newPauser The address to transfer pauserShip to. */ - function changePauser(address newPauser) public onlyPauser { + function changePauser(address newPauser) public onlyPauser whenNotPaused { _changePauser(newPauser); } @@ -62,7 +62,7 @@ abstract contract Pausable is Initializable, PausableUpgradeable { _pauser = newPauser; } - function renouncePauser() external virtual onlyPauser { + function renouncePauser() external virtual onlyPauser whenNotPaused { emit PauserChanged(_pauser, address(0)); _pauser = address(0); } diff --git a/contracts/test/previous-release/LiquidityFarming.sol b/contracts/test/previous-release/LiquidityFarming.sol new file mode 100644 index 0000000..780c6cc --- /dev/null +++ b/contracts/test/previous-release/LiquidityFarming.sol @@ -0,0 +1,1424 @@ +// Sources flattened with hardhat v2.8.4 https://hardhat.org + +// File @openzeppelin/contracts-upgradeable/token/ERC721/IERC721ReceiverUpgradeable.sol@v4.3.0 + +// SPDX-License-Identifier: MIT + +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 `IERC721.onERC721Received.selector`. + */ + function onERC721Received( + address operator, + address from, + uint256 tokenId, + bytes calldata data + ) external returns (bytes4); +} + + +// File @openzeppelin/contracts-upgradeable/interfaces/IERC721ReceiverUpgradeable.sol@v4.3.0 + + +pragma solidity ^0.8.0; + + +// File @openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol@v4.3.0 + + +pragma solidity ^0.8.0; + +/** + * @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 a proxied contract can't have 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. + * + * 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. + */ +abstract contract Initializable { + /** + * @dev Indicates that the contract has been initialized. + */ + bool private _initialized; + + /** + * @dev Indicates that the contract is in the process of being initialized. + */ + bool private _initializing; + + /** + * @dev Modifier to protect an initializer function from being invoked twice. + */ + modifier initializer() { + require(_initializing || !_initialized, "Initializable: contract is already initialized"); + + bool isTopLevelCall = !_initializing; + if (isTopLevelCall) { + _initializing = true; + _initialized = true; + } + + _; + + if (isTopLevelCall) { + _initializing = false; + } + } +} + + +// File @openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol@v4.3.0 + + +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 ReentrancyGuardUpgradeable is Initializable { + // 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; + + function __ReentrancyGuard_init() internal initializer { + __ReentrancyGuard_init_unchained(); + } + + function __ReentrancyGuard_init_unchained() internal initializer { + _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 make it call a + * `private` function that does the actual work. + */ + modifier nonReentrant() { + // On the first call to nonReentrant, _notEntered will be true + require(_status != _ENTERED, "ReentrancyGuard: reentrant call"); + + // Any calls to nonReentrant after this point will fail + _status = _ENTERED; + + _; + + // By storing the original value once again, a refund is triggered (see + // https://eips.ethereum.org/EIPS/eip-2200) + _status = _NOT_ENTERED; + } + uint256[49] private __gap; +} + + +// File @openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol@v4.3.0 + + +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 ContextUpgradeable is Initializable { + function __Context_init() internal initializer { + __Context_init_unchained(); + } + + function __Context_init_unchained() internal initializer { + } + function _msgSender() internal view virtual returns (address) { + return msg.sender; + } + + function _msgData() internal view virtual returns (bytes calldata) { + return msg.data; + } + uint256[50] private __gap; +} + + +// File @openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol@v4.3.0 + + +pragma solidity ^0.8.0; + + +/** + * @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 initializer { + __Context_init_unchained(); + __Ownable_init_unchained(); + } + + function __Ownable_init_unchained() internal initializer { + _setOwner(_msgSender()); + } + + /** + * @dev Returns the address of the current owner. + */ + function owner() public view virtual returns (address) { + return _owner; + } + + /** + * @dev Throws if called by any account other than the owner. + */ + modifier onlyOwner() { + require(owner() == _msgSender(), "Ownable: caller is not the owner"); + _; + } + + /** + * @dev Leaves the contract without owner. It will not be possible to call + * `onlyOwner` functions anymore. Can only be called by the current owner. + * + * NOTE: Renouncing ownership will leave the contract without an owner, + * thereby removing any functionality that is only available to the owner. + */ + function renounceOwnership() public virtual onlyOwner { + _setOwner(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"); + _setOwner(newOwner); + } + + function _setOwner(address newOwner) private { + address oldOwner = _owner; + _owner = newOwner; + emit OwnershipTransferred(oldOwner, newOwner); + } + uint256[49] private __gap; +} + + +// File @openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol@v4.3.0 + + +pragma solidity ^0.8.0; + +/** + * @dev Interface of the ERC20 standard as defined in the EIP. + */ +interface IERC20Upgradeable { + /** + * @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 `recipient`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address recipient, 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 `sender` to `recipient` 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 sender, + address recipient, + uint256 amount + ) external returns (bool); + + /** + * @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); +} + + +// File @openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol@v4.3.0 + + +pragma solidity ^0.8.0; + +/** + * @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 + * ==== + */ + function isContract(address account) internal view returns (bool) { + // This method relies on extcodesize, which returns 0 for contracts in + // construction, since the code is only stored at the end of the + // constructor execution. + + uint256 size; + assembly { + size := extcodesize(account) + } + return size > 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://diligence.consensys.net/posts/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.5.11/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 functionCall(target, data, "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"); + require(isContract(target), "Address: call to non-contract"); + + (bool success, bytes memory returndata) = target.call{value: value}(data); + return verifyCallResult(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) { + require(isContract(target), "Address: static call to non-contract"); + + (bool success, bytes memory returndata) = target.staticcall(data); + return verifyCallResult(success, returndata, errorMessage); + } + + /** + * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the + * revert reason 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 { + // 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 + + assembly { + let returndata_size := mload(returndata) + revert(add(32, returndata), returndata_size) + } + } else { + revert(errorMessage); + } + } + } +} + + +// File @openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol@v4.3.0 + + +pragma solidity ^0.8.0; + + +/** + * @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; + + function safeTransfer( + IERC20Upgradeable token, + address to, + uint256 value + ) internal { + _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); + } + + 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)); + } + + function safeIncreaseAllowance( + IERC20Upgradeable token, + address spender, + uint256 value + ) internal { + uint256 newAllowance = token.allowance(address(this), spender) + value; + _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); + } + + 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"); + uint256 newAllowance = oldAllowance - value; + _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); + } + } + + /** + * @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"); + if (returndata.length > 0) { + // Return data is optional + require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); + } + } +} + + +// File contracts/hyphen/metatx/ERC2771ContextUpgradeable.sol + + +pragma solidity 0.8.0; + + +/** + * @dev Context variant with ERC2771 support. + * Here _trustedForwarder is made internal instead of private + * so it can be changed via Child contracts with a setter method. + */ +abstract contract ERC2771ContextUpgradeable is Initializable, ContextUpgradeable { + address internal _trustedForwarder; + + function __ERC2771Context_init(address trustedForwarder) internal initializer { + __Context_init_unchained(); + __ERC2771Context_init_unchained(trustedForwarder); + } + + function __ERC2771Context_init_unchained(address trustedForwarder) internal initializer { + _trustedForwarder = trustedForwarder; + } + + function isTrustedForwarder(address forwarder) public view virtual returns (bool) { + return forwarder == _trustedForwarder; + } + + function _msgSender() internal view virtual override returns (address sender) { + if (isTrustedForwarder(msg.sender)) { + // The assembly code is more direct than the Solidity version using `abi.decode`. + assembly { + sender := shr(96, calldataload(sub(calldatasize(), 20))) + } + } else { + return super._msgSender(); + } + } + + function _msgData() internal view virtual override returns (bytes calldata) { + if (isTrustedForwarder(msg.sender)) { + return msg.data[:msg.data.length - 20]; + } else { + return super._msgData(); + } + } + uint256[49] private __gap; +} + + +// File @openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol@v4.3.0 + + +pragma solidity ^0.8.0; + + +/** + * @dev Contract module which allows children to implement an emergency stop + * mechanism that can be triggered by an authorized account. + * + * This module is used through inheritance. It will make available the + * modifiers `whenNotPaused` and `whenPaused`, which can be applied to + * the functions of your contract. Note that they will not be pausable by + * simply including this module, only once the modifiers are put in place. + */ +abstract contract PausableUpgradeable is Initializable, ContextUpgradeable { + /** + * @dev Emitted when the pause is triggered by `account`. + */ + event Paused(address account); + + /** + * @dev Emitted when the pause is lifted by `account`. + */ + event Unpaused(address account); + + bool private _paused; + + /** + * @dev Initializes the contract in unpaused state. + */ + function __Pausable_init() internal initializer { + __Context_init_unchained(); + __Pausable_init_unchained(); + } + + function __Pausable_init_unchained() internal initializer { + _paused = false; + } + + /** + * @dev Returns true if the contract is paused, and false otherwise. + */ + function paused() public view virtual returns (bool) { + return _paused; + } + + /** + * @dev Modifier to make a function callable only when the contract is not paused. + * + * Requirements: + * + * - The contract must not be paused. + */ + modifier whenNotPaused() { + require(!paused(), "Pausable: paused"); + _; + } + + /** + * @dev Modifier to make a function callable only when the contract is paused. + * + * Requirements: + * + * - The contract must be paused. + */ + modifier whenPaused() { + require(paused(), "Pausable: not paused"); + _; + } + + /** + * @dev Triggers stopped state. + * + * Requirements: + * + * - The contract must not be paused. + */ + function _pause() internal virtual whenNotPaused { + _paused = true; + emit Paused(_msgSender()); + } + + /** + * @dev Returns to normal state. + * + * Requirements: + * + * - The contract must be paused. + */ + function _unpause() internal virtual whenPaused { + _paused = false; + emit Unpaused(_msgSender()); + } + uint256[49] private __gap; +} + + +// File contracts/security/Pausable.sol + + +pragma solidity 0.8.0; + + +/** + * @dev Contract module which allows children to implement an emergency stop + * mechanism that can be triggered by an authorized account. + * + * This module is used through inheritance. It will make available the + * modifiers `whenNotPaused` and `whenPaused`, which can be applied to + * the functions of your contract. Note that they will not be pausable by + * simply including this module, only once the modifiers are put in place. + */ +abstract contract Pausable is Initializable, PausableUpgradeable { + address private _pauser; + + event PauserChanged(address indexed previousPauser, address indexed newPauser); + + /** + * @dev The pausable constructor sets the original `pauser` of the contract to the sender + * account & Initializes the contract in unpaused state.. + */ + function __Pausable_init(address pauser) internal initializer { + require(pauser != address(0), "Pauser Address cannot be 0"); + __Pausable_init(); + _pauser = pauser; + } + + /** + * @return true if `msg.sender` is the owner of the contract. + */ + function isPauser(address pauser) public view returns (bool) { + return pauser == _pauser; + } + + /** + * @dev Throws if called by any account other than the pauser. + */ + modifier onlyPauser() { + require(isPauser(msg.sender), "Only pauser is allowed to perform this operation"); + _; + } + + /** + * @dev Allows the current pauser to transfer control of the contract to a newPauser. + * @param newPauser The address to transfer pauserShip to. + */ + function changePauser(address newPauser) public onlyPauser { + _changePauser(newPauser); + } + + /** + * @dev Transfers control of the contract to a newPauser. + * @param newPauser The address to transfer ownership to. + */ + function _changePauser(address newPauser) internal { + require(newPauser != address(0)); + emit PauserChanged(_pauser, newPauser); + _pauser = newPauser; + } + + function renouncePauser() external virtual onlyPauser { + emit PauserChanged(_pauser, address(0)); + _pauser = address(0); + } + + function pause() public onlyPauser { + _pause(); + } + + function unpause() public onlyPauser { + _unpause(); + } +} + + +// File contracts/hyphen/structures/LpTokenMetadata.sol + +pragma solidity 0.8.0; + +struct LpTokenMetadata { + address token; + uint256 suppliedLiquidity; + uint256 shares; +} + + +// File contracts/hyphen/interfaces/ILPToken.sol + +pragma solidity 0.8.0; + +interface ILPToken { + function approve(address to, uint256 tokenId) external; + + function balanceOf(address _owner) external view returns (uint256); + + function exists(uint256 _tokenId) external view returns (bool); + + function getAllNftIdsByUser(address _owner) external view returns (uint256[] memory); + + function getApproved(uint256 tokenId) external view returns (address); + + function initialize( + string memory _name, + string memory _symbol, + address _trustedForwarder + ) external; + + function isApprovedForAll(address _owner, address operator) external view returns (bool); + + function isTrustedForwarder(address forwarder) external view returns (bool); + + function liquidityPoolAddress() external view returns (address); + + function mint(address _to) external returns (uint256); + + function name() external view returns (string memory); + + function owner() external view returns (address); + + function ownerOf(uint256 tokenId) external view returns (address); + + function paused() external view returns (bool); + + function renounceOwnership() external; + + function safeTransferFrom( + address from, + address to, + uint256 tokenId + ) external; + + function safeTransferFrom( + address from, + address to, + uint256 tokenId, + bytes memory _data + ) external; + + function setApprovalForAll(address operator, bool approved) external; + + function setLiquidityPool(address _lpm) external; + + function setWhiteListPeriodManager(address _whiteListPeriodManager) external; + + function supportsInterface(bytes4 interfaceId) external view returns (bool); + + function symbol() external view returns (string memory); + + function tokenByIndex(uint256 index) external view returns (uint256); + + function tokenMetadata(uint256) + external + view + returns ( + address token, + uint256 totalSuppliedLiquidity, + uint256 totalShares + ); + + function tokenOfOwnerByIndex(address _owner, uint256 index) external view returns (uint256); + + function tokenURI(uint256 tokenId) external view returns (string memory); + + function totalSupply() external view returns (uint256); + + function transferFrom( + address from, + address to, + uint256 tokenId + ) external; + + function transferOwnership(address newOwner) external; + + function updateTokenMetadata(uint256 _tokenId, LpTokenMetadata memory _lpTokenMetadata) external; + + function whiteListPeriodManager() external view returns (address); +} + + +// File contracts/hyphen/interfaces/ILiquidityProviders.sol + +pragma solidity 0.8.0; + +interface ILiquidityProviders { + function BASE_DIVISOR() external view returns (uint256); + + function initialize(address _trustedForwarder, address _lpToken) external; + + function addLPFee(address _token, uint256 _amount) external; + + function addNativeLiquidity() external; + + function addTokenLiquidity(address _token, uint256 _amount) external; + + function claimFee(uint256 _nftId) external; + + function getFeeAccumulatedOnNft(uint256 _nftId) external view returns (uint256); + + function getSuppliedLiquidityByToken(address tokenAddress) external view returns (uint256); + + function getTokenPriceInLPShares(address _baseToken) external view returns (uint256); + + function getTotalLPFeeByToken(address tokenAddress) external view returns (uint256); + + function getTotalReserveByToken(address tokenAddress) external view returns (uint256); + + function getSuppliedLiquidity(uint256 _nftId) external view returns (uint256); + + function increaseNativeLiquidity(uint256 _nftId) external; + + function increaseTokenLiquidity(uint256 _nftId, uint256 _amount) external; + + function isTrustedForwarder(address forwarder) external view returns (bool); + + function owner() external view returns (address); + + function paused() external view returns (bool); + + function removeLiquidity(uint256 _nftId, uint256 amount) external; + + function renounceOwnership() external; + + function setLiquidityPool(address _liquidityPool) external; + + function setLpToken(address _lpToken) external; + + function setWhiteListPeriodManager(address _whiteListPeriodManager) external; + + function sharesToTokenAmount(uint256 _shares, address _tokenAddress) external view returns (uint256); + + function totalLPFees(address) external view returns (uint256); + + function totalLiquidity(address) external view returns (uint256); + + function totalReserve(address) external view returns (uint256); + + function totalSharesMinted(address) external view returns (uint256); + + function transferOwnership(address newOwner) external; + + function whiteListPeriodManager() external view returns (address); + + function increaseCurrentLiquidity(address tokenAddress, uint256 amount) external; + + function decreaseCurrentLiquidity(address tokenAddress, uint256 amount) external; + + function getCurrentLiquidity(address tokenAddress) external view returns (uint256); +} + + +// File contracts/hyphen/LiquidityFarming.sol + +// $$\ $$\ $$\ $$\ $$\ $$\ $$$$$$$$\ $$\ +// $$ | \__| \__| $$ |\__| $$ | $$ _____| \__| +// $$ | $$\ $$$$$$\ $$\ $$\ $$\ $$$$$$$ |$$\ $$$$$$\ $$\ $$\ $$ | $$$$$$\ $$$$$$\ $$$$$$\$$$$\ $$\ $$$$$$$\ $$$$$$\ +// $$ | $$ |$$ __$$\ $$ | $$ |$$ |$$ __$$ |$$ |\_$$ _| $$ | $$ | $$$$$\ \____$$\ $$ __$$\ $$ _$$ _$$\ $$ |$$ __$$\ $$ __$$\ +// $$ | $$ |$$ / $$ |$$ | $$ |$$ |$$ / $$ |$$ | $$ | $$ | $$ | $$ __|$$$$$$$ |$$ | \__|$$ / $$ / $$ |$$ |$$ | $$ |$$ / $$ | +// $$ | $$ |$$ | $$ |$$ | $$ |$$ |$$ | $$ |$$ | $$ |$$\ $$ | $$ | $$ | $$ __$$ |$$ | $$ | $$ | $$ |$$ |$$ | $$ |$$ | $$ | +// $$$$$$$$\ $$ |\$$$$$$$ |\$$$$$$ |$$ |\$$$$$$$ |$$ | \$$$$ |\$$$$$$$ | $$ | \$$$$$$$ |$$ | $$ | $$ | $$ |$$ |$$ | $$ |\$$$$$$$ | +// \________|\__| \____$$ | \______/ \__| \_______|\__| \____/ \____$$ | \__| \_______|\__| \__| \__| \__|\__|\__| \__| \____$$ | +// $$ | $$\ $$ | $$\ $$ | +// $$ | \$$$$$$ | \$$$$$$ | +// \__| \______/ \______/ +// +pragma solidity 0.8.0; + +contract HyphenLiquidityFarmingOld is + Initializable, + ERC2771ContextUpgradeable, + OwnableUpgradeable, + Pausable, + ReentrancyGuardUpgradeable, + IERC721ReceiverUpgradeable +{ + using SafeERC20Upgradeable for IERC20Upgradeable; + + ILPToken public lpToken; + ILiquidityProviders public liquidityProviders; + + struct NFTInfo { + address payable staker; + uint256 rewardDebt; + uint256 unpaidRewards; + bool isStaked; + } + + struct PoolInfo { + uint256 accTokenPerShare; + uint256 lastRewardTime; + } + + struct RewardsPerSecondEntry { + uint256 rewardsPerSecond; + uint256 timestamp; + } + + /// @notice Mapping to track the rewarder pool. + mapping(address => PoolInfo) public poolInfo; + + /// @notice Info of each NFT that is staked. + mapping(uint256 => NFTInfo) public nftInfo; + + /// @notice Reward Token + mapping(address => address) public rewardTokens; + + /// @notice Staker => NFTs staked + mapping(address => uint256[]) public nftIdsStaked; + + /// @notice Token => Total Shares Staked + mapping(address => uint256) public totalSharesStaked; + + /// @notice Token => Reward Rate Updation history + mapping(address => RewardsPerSecondEntry[]) public rewardRateLog; + + uint256 private constant ACC_TOKEN_PRECISION = 1e12; + address internal constant NATIVE = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; + + event LogDeposit(address indexed user, address indexed baseToken, uint256 nftId); + event LogWithdraw(address indexed user, address baseToken, uint256 nftId, address indexed to); + event LogOnReward(address indexed user, address indexed baseToken, uint256 amount, address indexed to); + event LogUpdatePool(address indexed baseToken, uint256 lastRewardTime, uint256 lpSupply, uint256 accToken1PerShare); + event LogRewardPerSecond(address indexed baseToken, uint256 rewardPerSecond); + event LogRewardPoolInitialized(address _baseToken, address _rewardToken, uint256 _rewardPerSecond); + event LogNativeReceived(address indexed sender, uint256 value); + event LiquidityProviderUpdated(address indexed liquidityProviders); + + function initialize( + address _trustedForwarder, + address _pauser, + ILiquidityProviders _liquidityProviders, + ILPToken _lpToken + ) public initializer { + __ERC2771Context_init(_trustedForwarder); + __Ownable_init(); + __Pausable_init(_pauser); + __ReentrancyGuard_init(); + liquidityProviders = _liquidityProviders; + lpToken = _lpToken; + } + + /// @notice Initialize the rewarder pool. + /// @param _baseToken Base token to be used for the rewarder pool. + /// @param _rewardToken Reward token to be used for the rewarder pool. + /// @param _rewardPerSecond Reward rate per base token. + function initalizeRewardPool( + address _baseToken, + address _rewardToken, + uint256 _rewardPerSecond + ) external onlyOwner { + require(rewardTokens[_baseToken] == address(0), "ERR__POOL_ALREADY_INITIALIZED"); + require(_baseToken != address(0), "ERR__BASE_TOKEN_IS_ZERO"); + require(_rewardToken != address(0), "ERR_REWARD_TOKEN_IS_ZERO"); + rewardTokens[_baseToken] = _rewardToken; + rewardRateLog[_baseToken].push(RewardsPerSecondEntry(_rewardPerSecond, block.timestamp)); + emit LogRewardPoolInitialized(_baseToken, _rewardToken, _rewardPerSecond); + } + + function updateLiquidityProvider(ILiquidityProviders _liquidityProviders) external onlyOwner { + require(address(_liquidityProviders) != address(0), "ERR__LIQUIDITY_PROVIDER_IS_ZERO"); + liquidityProviders = _liquidityProviders; + emit LiquidityProviderUpdated(address(liquidityProviders)); + } + + function _sendErc20AndGetSentAmount( + IERC20Upgradeable _token, + uint256 _amount, + address _to + ) private returns (uint256) { + uint256 beforeBalance = _token.balanceOf(address(this)); + _token.safeTransfer(_to, _amount); + return beforeBalance - _token.balanceOf(address(this)); + } + + /// @notice Update the reward state of a nft, and if possible send reward funds to _to. + /// @param _nftId NFT ID that is being locked + /// @param _to Address to which rewards will be credited. + function _sendRewardsForNft(uint256 _nftId, address payable _to) internal { + NFTInfo storage nft = nftInfo[_nftId]; + require(nft.isStaked, "ERR__NFT_NOT_STAKED"); + + (address baseToken, , uint256 amount) = lpToken.tokenMetadata(_nftId); + amount /= liquidityProviders.BASE_DIVISOR(); + + PoolInfo memory pool = updatePool(baseToken); + uint256 pending; + uint256 amountSent; + if (amount > 0) { + pending = ((amount * pool.accTokenPerShare) / ACC_TOKEN_PRECISION) - nft.rewardDebt + nft.unpaidRewards; + if (rewardTokens[baseToken] == NATIVE) { + uint256 balance = address(this).balance; + if (pending > balance) { + unchecked { + nft.unpaidRewards = pending - balance; + } + (bool success, ) = _to.call{value: balance}(""); + require(success, "ERR__NATIVE_TRANSFER_FAILED"); + amountSent = balance; + } else { + nft.unpaidRewards = 0; + (bool success, ) = _to.call{value: pending}(""); + require(success, "ERR__NATIVE_TRANSFER_FAILED"); + amountSent = pending; + } + } else { + IERC20Upgradeable rewardToken = IERC20Upgradeable(rewardTokens[baseToken]); + uint256 balance = rewardToken.balanceOf(address(this)); + if (pending > balance) { + unchecked { + nft.unpaidRewards = pending - balance; + } + amountSent = _sendErc20AndGetSentAmount(rewardToken, balance, _to); + } else { + nft.unpaidRewards = 0; + amountSent = _sendErc20AndGetSentAmount(rewardToken, pending, _to); + } + } + } + nft.rewardDebt = (amount * pool.accTokenPerShare) / ACC_TOKEN_PRECISION; + emit LogOnReward(_msgSender(), baseToken, amountSent, _to); + } + + /// @notice Sets the sushi per second to be distributed. Can only be called by the owner. + /// @param _rewardPerSecond The amount of Sushi to be distributed per second. + function setRewardPerSecond(address _baseToken, uint256 _rewardPerSecond) external onlyOwner { + rewardRateLog[_baseToken].push(RewardsPerSecondEntry(_rewardPerSecond, block.timestamp)); + emit LogRewardPerSecond(_baseToken, _rewardPerSecond); + } + + /// @notice Allows owner to reclaim/withdraw any tokens (including reward tokens) held by this contract + /// @param _token Token to reclaim, use 0x00 for Ethereum + /// @param _amount Amount of tokens to reclaim + /// @param _to Receiver of the tokens, first of his name, rightful heir to the lost tokens, + /// reightful owner of the extra tokens, and ether, protector of mistaken transfers, mother of token reclaimers, + /// the Khaleesi of the Great Token Sea, the Unburnt, the Breaker of blockchains. + function reclaimTokens( + address _token, + uint256 _amount, + address payable _to + ) external onlyOwner { + require(_to != address(0), "ERR__TO_IS_ZERO"); + require(_amount != 0, "ERR__AMOUNT_IS_ZERO"); + if (_token == NATIVE) { + (bool success, ) = payable(_to).call{value: _amount}(""); + require(success, "ERR__NATIVE_TRANSFER_FAILED"); + } else { + IERC20Upgradeable(_token).safeTransfer(_to, _amount); + } + } + + /// @notice Deposit LP tokens + /// @param _nftId LP token nftId to deposit. + function deposit(uint256 _nftId, address payable _to) external whenNotPaused nonReentrant { + address msgSender = _msgSender(); + + require(_to != address(0), "ERR__TO_IS_ZERO"); + require( + lpToken.isApprovedForAll(msgSender, address(this)) || lpToken.getApproved(_nftId) == address(this), + "ERR__NOT_APPROVED" + ); + + NFTInfo storage nft = nftInfo[_nftId]; + require(!nft.isStaked, "ERR__NFT_ALREADY_STAKED"); + + (address baseToken, , uint256 amount) = lpToken.tokenMetadata(_nftId); + amount /= liquidityProviders.BASE_DIVISOR(); + + require(rewardTokens[baseToken] != address(0), "ERR__POOL_NOT_INITIALIZED"); + require(rewardRateLog[baseToken].length != 0, "ERR__POOL_NOT_INITIALIZED"); + + lpToken.safeTransferFrom(msgSender, address(this), _nftId); + + PoolInfo memory pool = updatePool(baseToken); + nft.isStaked = true; + nft.staker = _to; + nft.rewardDebt = (amount * pool.accTokenPerShare) / ACC_TOKEN_PRECISION; + + nftIdsStaked[_to].push(_nftId); + totalSharesStaked[baseToken] += amount; + + emit LogDeposit(msgSender, baseToken, _nftId); + } + + /// @notice Withdraw LP tokens + /// @param _nftId LP token nftId to withdraw. + /// @param _to The receiver of `amount` withdraw benefit. + function withdraw(uint256 _nftId, address payable _to) external whenNotPaused nonReentrant { + address msgSender = _msgSender(); + uint256 nftsStakedLength = nftIdsStaked[msgSender].length; + uint256 index; + for (index = 0; index < nftsStakedLength; ++index) { + if (nftIdsStaked[msgSender][index] == _nftId) { + break; + } + } + + require(index != nftsStakedLength, "ERR__NFT_NOT_STAKED"); + nftIdsStaked[msgSender][index] = nftIdsStaked[msgSender][nftIdsStaked[msgSender].length - 1]; + nftIdsStaked[msgSender].pop(); + + _sendRewardsForNft(_nftId, _to); + delete nftInfo[_nftId]; + + (address baseToken, , uint256 amount) = lpToken.tokenMetadata(_nftId); + amount /= liquidityProviders.BASE_DIVISOR(); + totalSharesStaked[baseToken] -= amount; + + lpToken.safeTransferFrom(address(this), msgSender, _nftId); + + emit LogWithdraw(msgSender, baseToken, _nftId, _to); + } + + /// @notice Extract all rewards without withdrawing LP tokens + /// @param _nftId LP token nftId for which rewards are to be withdrawn + /// @param _to The receiver of withdraw benefit. + function extractRewards(uint256 _nftId, address payable _to) external whenNotPaused nonReentrant { + require(nftInfo[_nftId].staker == _msgSender(), "ERR__NOT_OWNER"); + _sendRewardsForNft(_nftId, _to); + } + + /// @notice Calculates an up to date value of accTokenPerShare + /// @notice An updated value of accTokenPerShare is comitted to storage every time a new NFT is deposited, withdrawn or rewards are extracted + function getUpdatedAccTokenPerShare(address _baseToken) public view returns (uint256) { + uint256 accumulator = 0; + uint256 lastUpdatedTime = poolInfo[_baseToken].lastRewardTime; + uint256 counter = block.timestamp; + uint256 i = rewardRateLog[_baseToken].length - 1; + while (true) { + if (lastUpdatedTime >= counter) { + break; + } + unchecked { + accumulator += + rewardRateLog[_baseToken][i].rewardsPerSecond * + (counter - max(lastUpdatedTime, rewardRateLog[_baseToken][i].timestamp)); + } + counter = rewardRateLog[_baseToken][i].timestamp; + if (i == 0) { + break; + } + --i; + } + + // We know that during all the periods that were included in the current iterations, + // the value of totalSharesStaked[_baseToken] would not have changed, as we only consider the + // updates to the pool that happened after the lastUpdatedTime. + accumulator = (accumulator * ACC_TOKEN_PRECISION) / totalSharesStaked[_baseToken]; + return accumulator + poolInfo[_baseToken].accTokenPerShare; + } + + /// @notice View function to see pending Token + /// @param _nftId NFT for which pending tokens are to be viewed + /// @return pending reward for a given user. + function pendingToken(uint256 _nftId) external view returns (uint256) { + NFTInfo storage nft = nftInfo[_nftId]; + if (!nft.isStaked) { + return 0; + } + + (address baseToken, , uint256 amount) = lpToken.tokenMetadata(_nftId); + amount /= liquidityProviders.BASE_DIVISOR(); + + PoolInfo memory pool = poolInfo[baseToken]; + uint256 accToken1PerShare = pool.accTokenPerShare; + if (block.timestamp > pool.lastRewardTime && totalSharesStaked[baseToken] != 0) { + accToken1PerShare = getUpdatedAccTokenPerShare(baseToken); + } + return ((amount * accToken1PerShare) / ACC_TOKEN_PRECISION) - nft.rewardDebt + nft.unpaidRewards; + } + + /// @notice Update reward variables of the given pool. + /// @return pool Returns the pool that was updated. + function updatePool(address _baseToken) public whenNotPaused returns (PoolInfo memory pool) { + pool = poolInfo[_baseToken]; + if (block.timestamp > pool.lastRewardTime) { + if (totalSharesStaked[_baseToken] > 0) { + pool.accTokenPerShare = getUpdatedAccTokenPerShare(_baseToken); + } + pool.lastRewardTime = block.timestamp; + poolInfo[_baseToken] = pool; + emit LogUpdatePool(_baseToken, pool.lastRewardTime, totalSharesStaked[_baseToken], pool.accTokenPerShare); + } + } + + /// @notice View function to see the tokens staked by a given user. + /// @param _user Address of user. + function getNftIdsStaked(address _user) external view returns (uint256[] memory nftIds) { + nftIds = nftIdsStaked[_user]; + } + + function getRewardRatePerSecond(address _baseToken) external view returns (uint256) { + return rewardRateLog[_baseToken][rewardRateLog[_baseToken].length - 1].rewardsPerSecond; + } + + function onERC721Received( + address, + address, + uint256, + bytes calldata + ) external pure override(IERC721ReceiverUpgradeable) returns (bytes4) { + return bytes4(keccak256("onERC721Received(address,address,uint256,bytes)")); + } + + function _msgSender() + internal + view + virtual + override(ContextUpgradeable, ERC2771ContextUpgradeable) + returns (address sender) + { + return ERC2771ContextUpgradeable._msgSender(); + } + + function _msgData() + internal + view + virtual + override(ContextUpgradeable, ERC2771ContextUpgradeable) + returns (bytes calldata) + { + return ERC2771ContextUpgradeable._msgData(); + } + + receive() external payable { + emit LogNativeReceived(_msgSender(), msg.value); + } + + function max(uint256 _a, uint256 _b) private pure returns (uint256) { + return _a >= _b ? _a : _b; + } +} diff --git a/contracts/test/previous-release/LiquidityPool.sol b/contracts/test/previous-release/LiquidityPool.sol new file mode 100644 index 0000000..2d393f8 --- /dev/null +++ b/contracts/test/previous-release/LiquidityPool.sol @@ -0,0 +1,1475 @@ +// Sources flattened with hardhat v2.8.4 https://hardhat.org + +// File @openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol@v4.3.0 + +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +/** + * @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 a proxied contract can't have 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. + * + * 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. + */ +abstract contract Initializable { + /** + * @dev Indicates that the contract has been initialized. + */ + bool private _initialized; + + /** + * @dev Indicates that the contract is in the process of being initialized. + */ + bool private _initializing; + + /** + * @dev Modifier to protect an initializer function from being invoked twice. + */ + modifier initializer() { + require(_initializing || !_initialized, "Initializable: contract is already initialized"); + + bool isTopLevelCall = !_initializing; + if (isTopLevelCall) { + _initializing = true; + _initialized = true; + } + + _; + + if (isTopLevelCall) { + _initializing = false; + } + } +} + + +// File @openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol@v4.3.0 + + +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 ContextUpgradeable is Initializable { + function __Context_init() internal initializer { + __Context_init_unchained(); + } + + function __Context_init_unchained() internal initializer { + } + function _msgSender() internal view virtual returns (address) { + return msg.sender; + } + + function _msgData() internal view virtual returns (bytes calldata) { + return msg.data; + } + uint256[50] private __gap; +} + + +// File @openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol@v4.3.0 + + +pragma solidity ^0.8.0; + + +/** + * @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 initializer { + __Context_init_unchained(); + __Ownable_init_unchained(); + } + + function __Ownable_init_unchained() internal initializer { + _setOwner(_msgSender()); + } + + /** + * @dev Returns the address of the current owner. + */ + function owner() public view virtual returns (address) { + return _owner; + } + + /** + * @dev Throws if called by any account other than the owner. + */ + modifier onlyOwner() { + require(owner() == _msgSender(), "Ownable: caller is not the owner"); + _; + } + + /** + * @dev Leaves the contract without owner. It will not be possible to call + * `onlyOwner` functions anymore. Can only be called by the current owner. + * + * NOTE: Renouncing ownership will leave the contract without an owner, + * thereby removing any functionality that is only available to the owner. + */ + function renounceOwnership() public virtual onlyOwner { + _setOwner(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"); + _setOwner(newOwner); + } + + function _setOwner(address newOwner) private { + address oldOwner = _owner; + _owner = newOwner; + emit OwnershipTransferred(oldOwner, newOwner); + } + uint256[49] private __gap; +} + + +// File @openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol@v4.3.0 + + +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 ReentrancyGuardUpgradeable is Initializable { + // 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; + + function __ReentrancyGuard_init() internal initializer { + __ReentrancyGuard_init_unchained(); + } + + function __ReentrancyGuard_init_unchained() internal initializer { + _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 make it call a + * `private` function that does the actual work. + */ + modifier nonReentrant() { + // On the first call to nonReentrant, _notEntered will be true + require(_status != _ENTERED, "ReentrancyGuard: reentrant call"); + + // Any calls to nonReentrant after this point will fail + _status = _ENTERED; + + _; + + // By storing the original value once again, a refund is triggered (see + // https://eips.ethereum.org/EIPS/eip-2200) + _status = _NOT_ENTERED; + } + uint256[49] private __gap; +} + + +// File @openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol@v4.3.0 + + +pragma solidity ^0.8.0; + +/** + * @dev Interface of the ERC20 standard as defined in the EIP. + */ +interface IERC20Upgradeable { + /** + * @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 `recipient`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address recipient, 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 `sender` to `recipient` 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 sender, + address recipient, + uint256 amount + ) external returns (bool); + + /** + * @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); +} + + +// File @openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol@v4.3.0 + + +pragma solidity ^0.8.0; + +/** + * @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 + * ==== + */ + function isContract(address account) internal view returns (bool) { + // This method relies on extcodesize, which returns 0 for contracts in + // construction, since the code is only stored at the end of the + // constructor execution. + + uint256 size; + assembly { + size := extcodesize(account) + } + return size > 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://diligence.consensys.net/posts/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.5.11/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 functionCall(target, data, "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"); + require(isContract(target), "Address: call to non-contract"); + + (bool success, bytes memory returndata) = target.call{value: value}(data); + return verifyCallResult(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) { + require(isContract(target), "Address: static call to non-contract"); + + (bool success, bytes memory returndata) = target.staticcall(data); + return verifyCallResult(success, returndata, errorMessage); + } + + /** + * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the + * revert reason 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 { + // 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 + + assembly { + let returndata_size := mload(returndata) + revert(add(32, returndata), returndata_size) + } + } else { + revert(errorMessage); + } + } + } +} + + +// File @openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol@v4.3.0 + + +pragma solidity ^0.8.0; + + +/** + * @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; + + function safeTransfer( + IERC20Upgradeable token, + address to, + uint256 value + ) internal { + _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); + } + + 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)); + } + + function safeIncreaseAllowance( + IERC20Upgradeable token, + address spender, + uint256 value + ) internal { + uint256 newAllowance = token.allowance(address(this), spender) + value; + _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); + } + + 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"); + uint256 newAllowance = oldAllowance - value; + _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); + } + } + + /** + * @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"); + if (returndata.length > 0) { + // Return data is optional + require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); + } + } +} + + +// File contracts/hyphen/metatx/ERC2771ContextUpgradeable.sol + + +pragma solidity 0.8.0; + + +/** + * @dev Context variant with ERC2771 support. + * Here _trustedForwarder is made internal instead of private + * so it can be changed via Child contracts with a setter method. + */ +abstract contract ERC2771ContextUpgradeable is Initializable, ContextUpgradeable { + address internal _trustedForwarder; + + function __ERC2771Context_init(address trustedForwarder) internal initializer { + __Context_init_unchained(); + __ERC2771Context_init_unchained(trustedForwarder); + } + + function __ERC2771Context_init_unchained(address trustedForwarder) internal initializer { + _trustedForwarder = trustedForwarder; + } + + function isTrustedForwarder(address forwarder) public view virtual returns (bool) { + return forwarder == _trustedForwarder; + } + + function _msgSender() internal view virtual override returns (address sender) { + if (isTrustedForwarder(msg.sender)) { + // The assembly code is more direct than the Solidity version using `abi.decode`. + assembly { + sender := shr(96, calldataload(sub(calldatasize(), 20))) + } + } else { + return super._msgSender(); + } + } + + function _msgData() internal view virtual override returns (bytes calldata) { + if (isTrustedForwarder(msg.sender)) { + return msg.data[:msg.data.length - 20]; + } else { + return super._msgData(); + } + } + uint256[49] private __gap; +} + + +// File @openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol@v4.3.0 + + +pragma solidity ^0.8.0; + + +/** + * @dev Contract module which allows children to implement an emergency stop + * mechanism that can be triggered by an authorized account. + * + * This module is used through inheritance. It will make available the + * modifiers `whenNotPaused` and `whenPaused`, which can be applied to + * the functions of your contract. Note that they will not be pausable by + * simply including this module, only once the modifiers are put in place. + */ +abstract contract PausableUpgradeable is Initializable, ContextUpgradeable { + /** + * @dev Emitted when the pause is triggered by `account`. + */ + event Paused(address account); + + /** + * @dev Emitted when the pause is lifted by `account`. + */ + event Unpaused(address account); + + bool private _paused; + + /** + * @dev Initializes the contract in unpaused state. + */ + function __Pausable_init() internal initializer { + __Context_init_unchained(); + __Pausable_init_unchained(); + } + + function __Pausable_init_unchained() internal initializer { + _paused = false; + } + + /** + * @dev Returns true if the contract is paused, and false otherwise. + */ + function paused() public view virtual returns (bool) { + return _paused; + } + + /** + * @dev Modifier to make a function callable only when the contract is not paused. + * + * Requirements: + * + * - The contract must not be paused. + */ + modifier whenNotPaused() { + require(!paused(), "Pausable: paused"); + _; + } + + /** + * @dev Modifier to make a function callable only when the contract is paused. + * + * Requirements: + * + * - The contract must be paused. + */ + modifier whenPaused() { + require(paused(), "Pausable: not paused"); + _; + } + + /** + * @dev Triggers stopped state. + * + * Requirements: + * + * - The contract must not be paused. + */ + function _pause() internal virtual whenNotPaused { + _paused = true; + emit Paused(_msgSender()); + } + + /** + * @dev Returns to normal state. + * + * Requirements: + * + * - The contract must be paused. + */ + function _unpause() internal virtual whenPaused { + _paused = false; + emit Unpaused(_msgSender()); + } + uint256[49] private __gap; +} + + +// File contracts/security/Pausable.sol + + +pragma solidity 0.8.0; + + +/** + * @dev Contract module which allows children to implement an emergency stop + * mechanism that can be triggered by an authorized account. + * + * This module is used through inheritance. It will make available the + * modifiers `whenNotPaused` and `whenPaused`, which can be applied to + * the functions of your contract. Note that they will not be pausable by + * simply including this module, only once the modifiers are put in place. + */ +abstract contract Pausable is Initializable, PausableUpgradeable { + address private _pauser; + + event PauserChanged(address indexed previousPauser, address indexed newPauser); + + /** + * @dev The pausable constructor sets the original `pauser` of the contract to the sender + * account & Initializes the contract in unpaused state.. + */ + function __Pausable_init(address pauser) internal initializer { + require(pauser != address(0), "Pauser Address cannot be 0"); + __Pausable_init(); + _pauser = pauser; + } + + /** + * @return true if `msg.sender` is the owner of the contract. + */ + function isPauser(address pauser) public view returns (bool) { + return pauser == _pauser; + } + + /** + * @dev Throws if called by any account other than the pauser. + */ + modifier onlyPauser() { + require(isPauser(msg.sender), "Only pauser is allowed to perform this operation"); + _; + } + + /** + * @dev Allows the current pauser to transfer control of the contract to a newPauser. + * @param newPauser The address to transfer pauserShip to. + */ + function changePauser(address newPauser) public onlyPauser { + _changePauser(newPauser); + } + + /** + * @dev Transfers control of the contract to a newPauser. + * @param newPauser The address to transfer ownership to. + */ + function _changePauser(address newPauser) internal { + require(newPauser != address(0)); + emit PauserChanged(_pauser, newPauser); + _pauser = newPauser; + } + + function renouncePauser() external virtual onlyPauser { + emit PauserChanged(_pauser, address(0)); + _pauser = address(0); + } + + function pause() public onlyPauser { + _pause(); + } + + function unpause() public onlyPauser { + _unpause(); + } +} + + +// File contracts/hyphen/structures/TokenConfig.sol + +pragma solidity 0.8.0; + +struct TokenInfo { + uint256 transferOverhead; + bool supportedToken; + uint256 equilibriumFee; // Percentage fee Represented in basis points + uint256 maxFee; // Percentage fee Represented in basis points + TokenConfig tokenConfig; +} + +struct TokenConfig { + uint256 min; + uint256 max; +} + + +// File contracts/hyphen/interfaces/IExecutorManager.sol + + +pragma solidity 0.8.0; + +interface IExecutorManager { + function getExecutorStatus(address executor) external view returns (bool status); + + function getAllExecutors() external view returns (address[] memory); + + //Register new Executors + function addExecutors(address[] calldata executorArray) external; + + // Register single executor + function addExecutor(address executorAddress) external; + + //Remove registered Executors + function removeExecutors(address[] calldata executorArray) external; + + // Remove Register single executor + function removeExecutor(address executorAddress) external; +} + + +// File contracts/hyphen/interfaces/ILiquidityProviders.sol + +pragma solidity 0.8.0; + +interface ILiquidityProviders { + function BASE_DIVISOR() external view returns (uint256); + + function initialize(address _trustedForwarder, address _lpToken) external; + + function addLPFee(address _token, uint256 _amount) external; + + function addNativeLiquidity() external; + + function addTokenLiquidity(address _token, uint256 _amount) external; + + function claimFee(uint256 _nftId) external; + + function getFeeAccumulatedOnNft(uint256 _nftId) external view returns (uint256); + + function getSuppliedLiquidityByToken(address tokenAddress) external view returns (uint256); + + function getTokenPriceInLPShares(address _baseToken) external view returns (uint256); + + function getTotalLPFeeByToken(address tokenAddress) external view returns (uint256); + + function getTotalReserveByToken(address tokenAddress) external view returns (uint256); + + function getSuppliedLiquidity(uint256 _nftId) external view returns (uint256); + + function increaseNativeLiquidity(uint256 _nftId) external; + + function increaseTokenLiquidity(uint256 _nftId, uint256 _amount) external; + + function isTrustedForwarder(address forwarder) external view returns (bool); + + function owner() external view returns (address); + + function paused() external view returns (bool); + + function removeLiquidity(uint256 _nftId, uint256 amount) external; + + function renounceOwnership() external; + + function setLiquidityPool(address _liquidityPool) external; + + function setLpToken(address _lpToken) external; + + function setWhiteListPeriodManager(address _whiteListPeriodManager) external; + + function sharesToTokenAmount(uint256 _shares, address _tokenAddress) external view returns (uint256); + + function totalLPFees(address) external view returns (uint256); + + function totalLiquidity(address) external view returns (uint256); + + function totalReserve(address) external view returns (uint256); + + function totalSharesMinted(address) external view returns (uint256); + + function transferOwnership(address newOwner) external; + + function whiteListPeriodManager() external view returns (address); + + function increaseCurrentLiquidity(address tokenAddress, uint256 amount) external; + + function decreaseCurrentLiquidity(address tokenAddress, uint256 amount) external; + + function getCurrentLiquidity(address tokenAddress) external view returns (uint256); +} + + +// File contracts/interfaces/IERC20Permit.sol + +pragma solidity 0.8.0; + +interface IERC20Detailed is IERC20Upgradeable { + function name() external view returns(string memory); + function decimals() external view returns(uint256); +} + +interface IERC20Nonces is IERC20Detailed { + function nonces(address holder) external view returns(uint); +} + +interface IERC20Permit is IERC20Nonces { + function permit(address holder, address spender, uint256 nonce, uint256 expiry, + bool allowed, uint8 v, bytes32 r, bytes32 s) external; + + function permit(address holder, address spender, uint256 value, uint256 expiry, + uint8 v, bytes32 r, bytes32 s) external; +} + + +// File contracts/hyphen/interfaces/ITokenManager.sol + +pragma solidity 0.8.0; + +interface ITokenManager { + function getEquilibriumFee(address tokenAddress) external view returns (uint256); + + function getMaxFee(address tokenAddress) external view returns (uint256); + + function changeFee( + address tokenAddress, + uint256 _equilibriumFee, + uint256 _maxFee + ) external; + + function tokensInfo(address tokenAddress) + external + view + returns ( + uint256 transferOverhead, + bool supportedToken, + uint256 equilibriumFee, + uint256 maxFee, + TokenConfig memory config + ); + + function getTokensInfo(address tokenAddress) external view returns (TokenInfo memory); + + function getDepositConfig(uint256 toChainId, address tokenAddress) external view returns (TokenConfig memory); + + function getTransferConfig(address tokenAddress) external view returns (TokenConfig memory); +} + + +// File contracts/hyphen/LiquidityPool.sol + +// $$\ $$\ $$\ $$$$$$$\ $$\ +// $$ | $$ | $$ | $$ __$$\ $$ | +// $$ | $$ |$$\ $$\ $$$$$$\ $$$$$$$\ $$$$$$\ $$$$$$$\ $$ | $$ | $$$$$$\ $$$$$$\ $$ | +// $$$$$$$$ |$$ | $$ |$$ __$$\ $$ __$$\ $$ __$$\ $$ __$$\ $$$$$$$ |$$ __$$\ $$ __$$\ $$ | +// $$ __$$ |$$ | $$ |$$ / $$ |$$ | $$ |$$$$$$$$ |$$ | $$ | $$ ____/ $$ / $$ |$$ / $$ |$$ | +// $$ | $$ |$$ | $$ |$$ | $$ |$$ | $$ |$$ ____|$$ | $$ | $$ | $$ | $$ |$$ | $$ |$$ | +// $$ | $$ |\$$$$$$$ |$$$$$$$ |$$ | $$ |\$$$$$$$\ $$ | $$ | $$ | \$$$$$$ |\$$$$$$ |$$ | +// \__| \__| \____$$ |$$ ____/ \__| \__| \_______|\__| \__| \__| \______/ \______/ \__| +// $$\ $$ |$$ | +// \$$$$$$ |$$ | +// \______/ \__| +// + +pragma solidity 0.8.0; +pragma abicoder v2; + + + + + + + + + + + + +contract LiquidityPoolOld is + Initializable, + ReentrancyGuardUpgradeable, + Pausable, + OwnableUpgradeable, + ERC2771ContextUpgradeable +{ + address private constant NATIVE = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; + uint256 private constant BASE_DIVISOR = 10000000000; // Basis Points * 100 for better accuracy + + uint256 public baseGas; + + IExecutorManager private executorManager; + ITokenManager public tokenManager; + ILiquidityProviders public liquidityProviders; + + struct PermitRequest { + uint256 nonce; + uint256 expiry; + bool allowed; + uint8 v; + bytes32 r; + bytes32 s; + } + + mapping(bytes32 => bool) public processedHash; + mapping(address => uint256) public gasFeeAccumulatedByToken; + + // Gas fee accumulated by token address => executor address + mapping(address => mapping(address => uint256)) public gasFeeAccumulated; + + // Incentive Pool amount per token address + mapping(address => uint256) public incentivePool; + + event AssetSent( + address indexed asset, + uint256 indexed amount, + uint256 indexed transferredAmount, + address target, + bytes depositHash, + uint256 fromChainId, + uint256 lpFee, + uint256 transferFee, + uint256 gasFee + ); + event Received(address indexed from, uint256 indexed amount); + event Deposit( + address indexed from, + address indexed tokenAddress, + address indexed receiver, + uint256 toChainId, + uint256 amount, + uint256 reward, + string tag + ); + event GasFeeWithdraw(address indexed tokenAddress, address indexed owner, uint256 indexed amount); + event TrustedForwarderChanged(address indexed forwarderAddress); + event LiquidityProvidersChanged(address indexed liquidityProvidersAddress); + event TokenManagerChanged(address indexed tokenManagerAddress); + event EthReceived(address, uint256); + + // MODIFIERS + modifier onlyExecutor() { + require(executorManager.getExecutorStatus(_msgSender()), "Only executor is allowed"); + _; + } + + modifier onlyLiquidityProviders() { + require(_msgSender() == address(liquidityProviders), "Only liquidityProviders is allowed"); + _; + } + + modifier tokenChecks(address tokenAddress) { + (, bool supportedToken, , , ) = tokenManager.tokensInfo(tokenAddress); + require(supportedToken, "Token not supported"); + _; + } + + function initialize( + address _executorManagerAddress, + address _pauser, + address _trustedForwarder, + address _tokenManager, + address _liquidityProviders + ) public initializer { + require(_executorManagerAddress != address(0), "ExecutorManager cannot be 0x0"); + require(_trustedForwarder != address(0), "TrustedForwarder cannot be 0x0"); + require(_liquidityProviders != address(0), "LiquidityProviders cannot be 0x0"); + __ERC2771Context_init(_trustedForwarder); + __ReentrancyGuard_init(); + __Ownable_init(); + __Pausable_init(_pauser); + executorManager = IExecutorManager(_executorManagerAddress); + tokenManager = ITokenManager(_tokenManager); + liquidityProviders = ILiquidityProviders(_liquidityProviders); + baseGas = 21000; + } + + function setTrustedForwarder(address trustedForwarder) external onlyOwner { + require(trustedForwarder != address(0), "TrustedForwarder can't be 0"); + _trustedForwarder = trustedForwarder; + emit TrustedForwarderChanged(trustedForwarder); + } + + function setLiquidityProviders(address _liquidityProviders) external onlyOwner { + require(_liquidityProviders != address(0), "LiquidityProviders can't be 0"); + liquidityProviders = ILiquidityProviders(_liquidityProviders); + emit LiquidityProvidersChanged(_liquidityProviders); + } + + function setTokenManager(address _tokenManager) external onlyOwner { + require(_tokenManager != address(0), "TokenManager can't be 0"); + tokenManager = ITokenManager(_tokenManager); + emit TokenManagerChanged(_tokenManager); + } + + function setBaseGas(uint128 gas) external onlyOwner { + baseGas = gas; + } + + function getExecutorManager() external view returns (address) { + return address(executorManager); + } + + function setExecutorManager(address _executorManagerAddress) external onlyOwner { + require(_executorManagerAddress != address(0), "Executor Manager cannot be 0"); + executorManager = IExecutorManager(_executorManagerAddress); + } + + function getCurrentLiquidity(address tokenAddress) public view returns (uint256 currentLiquidity) { + uint256 liquidityPoolBalance = liquidityProviders.getCurrentLiquidity(tokenAddress); + + currentLiquidity = + liquidityPoolBalance - + liquidityProviders.totalLPFees(tokenAddress) - + gasFeeAccumulatedByToken[tokenAddress] - + incentivePool[tokenAddress]; + } + + /** + * @dev Function used to deposit tokens into pool to initiate a cross chain token transfer. + * @param toChainId Chain id where funds needs to be transfered + * @param tokenAddress ERC20 Token address that needs to be transfered + * @param receiver Address on toChainId where tokens needs to be transfered + * @param amount Amount of token being transfered + */ + function depositErc20( + uint256 toChainId, + address tokenAddress, + address receiver, + uint256 amount, + string calldata tag + ) public tokenChecks(tokenAddress) whenNotPaused nonReentrant { + TokenConfig memory config = tokenManager.getDepositConfig(toChainId, tokenAddress); + + require(config.min <= amount && config.max >= amount, "Deposit amount not in Cap limit"); + require(receiver != address(0), "Receiver address cannot be 0"); + require(amount != 0, "Amount cannot be 0"); + address sender = _msgSender(); + + uint256 rewardAmount = getRewardAmount(amount, tokenAddress); + if (rewardAmount != 0) { + incentivePool[tokenAddress] = incentivePool[tokenAddress] - rewardAmount; + } + liquidityProviders.increaseCurrentLiquidity(tokenAddress, amount); + SafeERC20Upgradeable.safeTransferFrom(IERC20Upgradeable(tokenAddress), sender, address(this), amount); + // Emit (amount + reward amount) in event + emit Deposit(sender, tokenAddress, receiver, toChainId, amount + rewardAmount, rewardAmount, tag); + } + + function getRewardAmount(uint256 amount, address tokenAddress) public view returns (uint256 rewardAmount) { + uint256 currentLiquidity = getCurrentLiquidity(tokenAddress); + uint256 providedLiquidity = liquidityProviders.getSuppliedLiquidityByToken(tokenAddress); + if (currentLiquidity < providedLiquidity) { + uint256 liquidityDifference = providedLiquidity - currentLiquidity; + if (amount >= liquidityDifference) { + rewardAmount = incentivePool[tokenAddress]; + } else { + // Multiply by 10000000000 to avoid 0 reward amount for small amount and liquidity difference + rewardAmount = (amount * incentivePool[tokenAddress] * 10000000000) / liquidityDifference; + rewardAmount = rewardAmount / 10000000000; + } + } + } + + /** + * DAI permit and Deposit. + */ + function permitAndDepositErc20( + address tokenAddress, + address receiver, + uint256 amount, + uint256 toChainId, + PermitRequest calldata permitOptions, + string calldata tag + ) external { + IERC20Permit(tokenAddress).permit( + _msgSender(), + address(this), + permitOptions.nonce, + permitOptions.expiry, + permitOptions.allowed, + permitOptions.v, + permitOptions.r, + permitOptions.s + ); + depositErc20(toChainId, tokenAddress, receiver, amount, tag); + } + + /** + * EIP2612 and Deposit. + */ + function permitEIP2612AndDepositErc20( + address tokenAddress, + address receiver, + uint256 amount, + uint256 toChainId, + PermitRequest calldata permitOptions, + string calldata tag + ) external { + IERC20Permit(tokenAddress).permit( + _msgSender(), + address(this), + amount, + permitOptions.expiry, + permitOptions.v, + permitOptions.r, + permitOptions.s + ); + depositErc20(toChainId, tokenAddress, receiver, amount, tag); + } + + /** + * @dev Function used to deposit native token into pool to initiate a cross chain token transfer. + * @param receiver Address on toChainId where tokens needs to be transfered + * @param toChainId Chain id where funds needs to be transfered + */ + function depositNative( + address receiver, + uint256 toChainId, + string calldata tag + ) external payable whenNotPaused nonReentrant { + require( + tokenManager.getDepositConfig(toChainId, NATIVE).min <= msg.value && + tokenManager.getDepositConfig(toChainId, NATIVE).max >= msg.value, + "Deposit amount not in Cap limit" + ); + require(receiver != address(0), "Receiver address cannot be 0"); + require(msg.value != 0, "Amount cannot be 0"); + + uint256 rewardAmount = getRewardAmount(msg.value, NATIVE); + if (rewardAmount != 0) { + incentivePool[NATIVE] = incentivePool[NATIVE] - rewardAmount; + } + liquidityProviders.increaseCurrentLiquidity(NATIVE, msg.value); + emit Deposit(_msgSender(), NATIVE, receiver, toChainId, msg.value + rewardAmount, rewardAmount, tag); + } + + function sendFundsToUser( + address tokenAddress, + uint256 amount, + address payable receiver, + bytes calldata depositHash, + uint256 tokenGasPrice, + uint256 fromChainId + ) external nonReentrant onlyExecutor whenNotPaused { + uint256 initialGas = gasleft(); + TokenConfig memory config = tokenManager.getTransferConfig(tokenAddress); + require(config.min <= amount && config.max >= amount, "Withdraw amount not in Cap limit"); + require(receiver != address(0), "Bad receiver address"); + + (bytes32 hashSendTransaction, bool status) = checkHashStatus(tokenAddress, amount, receiver, depositHash); + + require(!status, "Already Processed"); + processedHash[hashSendTransaction] = true; + + // uint256 amountToTransfer, uint256 lpFee, uint256 transferFeeAmount, uint256 gasFee + uint256[4] memory transferDetails = getAmountToTransfer(initialGas, tokenAddress, amount, tokenGasPrice); + + liquidityProviders.decreaseCurrentLiquidity(tokenAddress, transferDetails[0]); + + if (tokenAddress == NATIVE) { + (bool success, ) = receiver.call{value: transferDetails[0]}(""); + require(success, "Native Transfer Failed"); + } else { + SafeERC20Upgradeable.safeTransfer(IERC20Upgradeable(tokenAddress), receiver, transferDetails[0]); + } + + emit AssetSent( + tokenAddress, + amount, + transferDetails[0], + receiver, + depositHash, + fromChainId, + transferDetails[1], + transferDetails[2], + transferDetails[3] + ); + } + + /** + * @dev Internal function to calculate amount of token that needs to be transfered afetr deducting all required fees. + * Fee to be deducted includes gas fee, lp fee and incentive pool amount if needed. + * @param initialGas Gas provided initially before any calculations began + * @param tokenAddress Token address for which calculation needs to be done + * @param amount Amount of token to be transfered before deducting the fee + * @param tokenGasPrice Gas price in the token being transfered to be used to calculate gas fee + * @return [ amountToTransfer, lpFee, transferFeeAmount, gasFee ] + */ + function getAmountToTransfer( + uint256 initialGas, + address tokenAddress, + uint256 amount, + uint256 tokenGasPrice + ) internal returns (uint256[4] memory) { + TokenInfo memory tokenInfo = tokenManager.getTokensInfo(tokenAddress); + uint256 transferFeePerc = _getTransferFee(tokenAddress, amount, tokenInfo); + uint256 lpFee; + if (transferFeePerc > tokenInfo.equilibriumFee) { + // Here add some fee to incentive pool also + lpFee = (amount * tokenInfo.equilibriumFee) / BASE_DIVISOR; + unchecked { + incentivePool[tokenAddress] += (amount * (transferFeePerc - tokenInfo.equilibriumFee)) / BASE_DIVISOR; + } + } else { + lpFee = (amount * transferFeePerc) / BASE_DIVISOR; + } + uint256 transferFeeAmount = (amount * transferFeePerc) / BASE_DIVISOR; + + liquidityProviders.addLPFee(tokenAddress, lpFee); + + uint256 totalGasUsed = initialGas + tokenInfo.transferOverhead + baseGas - gasleft(); + + uint256 gasFee = totalGasUsed * tokenGasPrice; + gasFeeAccumulatedByToken[tokenAddress] += gasFee; + gasFeeAccumulated[tokenAddress][_msgSender()] += gasFee; + uint256 amountToTransfer = amount - (transferFeeAmount + gasFee); + return [amountToTransfer, lpFee, transferFeeAmount, gasFee]; + } + + function _getTransferFee( + address tokenAddress, + uint256 amount, + TokenInfo memory tokenInfo + ) private view returns (uint256 fee) { + uint256 currentLiquidity = getCurrentLiquidity(tokenAddress); + uint256 providedLiquidity = liquidityProviders.getSuppliedLiquidityByToken(tokenAddress); + + uint256 resultingLiquidity = currentLiquidity - amount; + + // Fee is represented in basis points * 10 for better accuracy + uint256 numerator = providedLiquidity * tokenInfo.equilibriumFee * tokenInfo.maxFee; // F(max) * F(e) * L(e) + uint256 denominator = tokenInfo.equilibriumFee * + providedLiquidity + + (tokenInfo.maxFee - tokenInfo.equilibriumFee) * + resultingLiquidity; // F(e) * L(e) + (F(max) - F(e)) * L(r) + + if (denominator == 0) { + fee = 0; + } else { + fee = numerator / denominator; + } + } + + function getTransferFee(address tokenAddress, uint256 amount) external view returns (uint256) { + return _getTransferFee(tokenAddress, amount, tokenManager.getTokensInfo(tokenAddress)); + } + + function checkHashStatus( + address tokenAddress, + uint256 amount, + address payable receiver, + bytes calldata depositHash + ) public view returns (bytes32 hashSendTransaction, bool status) { + hashSendTransaction = keccak256(abi.encode(tokenAddress, amount, receiver, keccak256(depositHash))); + + status = processedHash[hashSendTransaction]; + } + + function withdrawErc20GasFee(address tokenAddress) external onlyExecutor whenNotPaused nonReentrant { + require(tokenAddress != NATIVE, "Can't withdraw native token fee"); + // uint256 gasFeeAccumulated = gasFeeAccumulatedByToken[tokenAddress]; + uint256 _gasFeeAccumulated = gasFeeAccumulated[tokenAddress][_msgSender()]; + require(_gasFeeAccumulated != 0, "Gas Fee earned is 0"); + gasFeeAccumulatedByToken[tokenAddress] = gasFeeAccumulatedByToken[tokenAddress] - _gasFeeAccumulated; + gasFeeAccumulated[tokenAddress][_msgSender()] = 0; + SafeERC20Upgradeable.safeTransfer(IERC20Upgradeable(tokenAddress), _msgSender(), _gasFeeAccumulated); + emit GasFeeWithdraw(tokenAddress, _msgSender(), _gasFeeAccumulated); + } + + function withdrawNativeGasFee() external onlyExecutor whenNotPaused nonReentrant { + uint256 _gasFeeAccumulated = gasFeeAccumulated[NATIVE][_msgSender()]; + require(_gasFeeAccumulated != 0, "Gas Fee earned is 0"); + gasFeeAccumulatedByToken[NATIVE] = gasFeeAccumulatedByToken[NATIVE] - _gasFeeAccumulated; + gasFeeAccumulated[NATIVE][_msgSender()] = 0; + (bool success, ) = payable(_msgSender()).call{value: _gasFeeAccumulated}(""); + require(success, "Native Transfer Failed"); + + emit GasFeeWithdraw(address(this), _msgSender(), _gasFeeAccumulated); + } + + function transfer( + address _tokenAddress, + address receiver, + uint256 _tokenAmount + ) external whenNotPaused onlyLiquidityProviders nonReentrant { + require(receiver != address(0), "Invalid receiver"); + if (_tokenAddress == NATIVE) { + require(address(this).balance >= _tokenAmount, "ERR__INSUFFICIENT_BALANCE"); + (bool success, ) = receiver.call{value: _tokenAmount}(""); + require(success, "ERR__NATIVE_TRANSFER_FAILED"); + } else { + IERC20Upgradeable baseToken = IERC20Upgradeable(_tokenAddress); + require(baseToken.balanceOf(address(this)) >= _tokenAmount, "ERR__INSUFFICIENT_BALANCE"); + SafeERC20Upgradeable.safeTransfer(baseToken, receiver, _tokenAmount); + } + } + + function _msgSender() + internal + view + virtual + override(ContextUpgradeable, ERC2771ContextUpgradeable) + returns (address sender) + { + return ERC2771ContextUpgradeable._msgSender(); + } + + function _msgData() + internal + view + virtual + override(ContextUpgradeable, ERC2771ContextUpgradeable) + returns (bytes calldata) + { + return ERC2771ContextUpgradeable._msgData(); + } + + receive() external payable { + emit EthReceived(_msgSender(), msg.value); + } +} diff --git a/contracts/test/previous-release/LiquidityProviders.sol b/contracts/test/previous-release/LiquidityProviders.sol new file mode 100644 index 0000000..8437ac5 --- /dev/null +++ b/contracts/test/previous-release/LiquidityProviders.sol @@ -0,0 +1,1637 @@ +// Sources flattened with hardhat v2.8.4 https://hardhat.org + +// File @openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol@v4.3.0 + +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +/** + * @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 a proxied contract can't have 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. + * + * 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. + */ +abstract contract Initializable { + /** + * @dev Indicates that the contract has been initialized. + */ + bool private _initialized; + + /** + * @dev Indicates that the contract is in the process of being initialized. + */ + bool private _initializing; + + /** + * @dev Modifier to protect an initializer function from being invoked twice. + */ + modifier initializer() { + require(_initializing || !_initialized, "Initializable: contract is already initialized"); + + bool isTopLevelCall = !_initializing; + if (isTopLevelCall) { + _initializing = true; + _initialized = true; + } + + _; + + if (isTopLevelCall) { + _initializing = false; + } + } +} + + +// File @openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol@v4.3.0 + + +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 ReentrancyGuardUpgradeable is Initializable { + // 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; + + function __ReentrancyGuard_init() internal initializer { + __ReentrancyGuard_init_unchained(); + } + + function __ReentrancyGuard_init_unchained() internal initializer { + _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 make it call a + * `private` function that does the actual work. + */ + modifier nonReentrant() { + // On the first call to nonReentrant, _notEntered will be true + require(_status != _ENTERED, "ReentrancyGuard: reentrant call"); + + // Any calls to nonReentrant after this point will fail + _status = _ENTERED; + + _; + + // By storing the original value once again, a refund is triggered (see + // https://eips.ethereum.org/EIPS/eip-2200) + _status = _NOT_ENTERED; + } + uint256[49] private __gap; +} + + +// File @openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol@v4.3.0 + + +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 ContextUpgradeable is Initializable { + function __Context_init() internal initializer { + __Context_init_unchained(); + } + + function __Context_init_unchained() internal initializer { + } + function _msgSender() internal view virtual returns (address) { + return msg.sender; + } + + function _msgData() internal view virtual returns (bytes calldata) { + return msg.data; + } + uint256[50] private __gap; +} + + +// File @openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol@v4.3.0 + + +pragma solidity ^0.8.0; + + +/** + * @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 initializer { + __Context_init_unchained(); + __Ownable_init_unchained(); + } + + function __Ownable_init_unchained() internal initializer { + _setOwner(_msgSender()); + } + + /** + * @dev Returns the address of the current owner. + */ + function owner() public view virtual returns (address) { + return _owner; + } + + /** + * @dev Throws if called by any account other than the owner. + */ + modifier onlyOwner() { + require(owner() == _msgSender(), "Ownable: caller is not the owner"); + _; + } + + /** + * @dev Leaves the contract without owner. It will not be possible to call + * `onlyOwner` functions anymore. Can only be called by the current owner. + * + * NOTE: Renouncing ownership will leave the contract without an owner, + * thereby removing any functionality that is only available to the owner. + */ + function renounceOwnership() public virtual onlyOwner { + _setOwner(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"); + _setOwner(newOwner); + } + + function _setOwner(address newOwner) private { + address oldOwner = _owner; + _owner = newOwner; + emit OwnershipTransferred(oldOwner, newOwner); + } + uint256[49] private __gap; +} + + +// File @openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol@v4.3.0 + + +pragma solidity ^0.8.0; + +/** + * @dev Interface of the ERC20 standard as defined in the EIP. + */ +interface IERC20Upgradeable { + /** + * @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 `recipient`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address recipient, 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 `sender` to `recipient` 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 sender, + address recipient, + uint256 amount + ) external returns (bool); + + /** + * @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); +} + + +// File @openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol@v4.3.0 + + +pragma solidity ^0.8.0; + +/** + * @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 + * ==== + */ + function isContract(address account) internal view returns (bool) { + // This method relies on extcodesize, which returns 0 for contracts in + // construction, since the code is only stored at the end of the + // constructor execution. + + uint256 size; + assembly { + size := extcodesize(account) + } + return size > 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://diligence.consensys.net/posts/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.5.11/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 functionCall(target, data, "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"); + require(isContract(target), "Address: call to non-contract"); + + (bool success, bytes memory returndata) = target.call{value: value}(data); + return verifyCallResult(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) { + require(isContract(target), "Address: static call to non-contract"); + + (bool success, bytes memory returndata) = target.staticcall(data); + return verifyCallResult(success, returndata, errorMessage); + } + + /** + * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the + * revert reason 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 { + // 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 + + assembly { + let returndata_size := mload(returndata) + revert(add(32, returndata), returndata_size) + } + } else { + revert(errorMessage); + } + } + } +} + + +// File @openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol@v4.3.0 + + +pragma solidity ^0.8.0; + + +/** + * @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; + + function safeTransfer( + IERC20Upgradeable token, + address to, + uint256 value + ) internal { + _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); + } + + 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)); + } + + function safeIncreaseAllowance( + IERC20Upgradeable token, + address spender, + uint256 value + ) internal { + uint256 newAllowance = token.allowance(address(this), spender) + value; + _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); + } + + 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"); + uint256 newAllowance = oldAllowance - value; + _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); + } + } + + /** + * @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"); + if (returndata.length > 0) { + // Return data is optional + require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); + } + } +} + + +// File contracts/hyphen/metatx/ERC2771ContextUpgradeable.sol + + +pragma solidity 0.8.0; + + +/** + * @dev Context variant with ERC2771 support. + * Here _trustedForwarder is made internal instead of private + * so it can be changed via Child contracts with a setter method. + */ +abstract contract ERC2771ContextUpgradeable is Initializable, ContextUpgradeable { + address internal _trustedForwarder; + + function __ERC2771Context_init(address trustedForwarder) internal initializer { + __Context_init_unchained(); + __ERC2771Context_init_unchained(trustedForwarder); + } + + function __ERC2771Context_init_unchained(address trustedForwarder) internal initializer { + _trustedForwarder = trustedForwarder; + } + + function isTrustedForwarder(address forwarder) public view virtual returns (bool) { + return forwarder == _trustedForwarder; + } + + function _msgSender() internal view virtual override returns (address sender) { + if (isTrustedForwarder(msg.sender)) { + // The assembly code is more direct than the Solidity version using `abi.decode`. + assembly { + sender := shr(96, calldataload(sub(calldatasize(), 20))) + } + } else { + return super._msgSender(); + } + } + + function _msgData() internal view virtual override returns (bytes calldata) { + if (isTrustedForwarder(msg.sender)) { + return msg.data[:msg.data.length - 20]; + } else { + return super._msgData(); + } + } + uint256[49] private __gap; +} + + +// File @openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol@v4.3.0 + + +pragma solidity ^0.8.0; + + +/** + * @dev Contract module which allows children to implement an emergency stop + * mechanism that can be triggered by an authorized account. + * + * This module is used through inheritance. It will make available the + * modifiers `whenNotPaused` and `whenPaused`, which can be applied to + * the functions of your contract. Note that they will not be pausable by + * simply including this module, only once the modifiers are put in place. + */ +abstract contract PausableUpgradeable is Initializable, ContextUpgradeable { + /** + * @dev Emitted when the pause is triggered by `account`. + */ + event Paused(address account); + + /** + * @dev Emitted when the pause is lifted by `account`. + */ + event Unpaused(address account); + + bool private _paused; + + /** + * @dev Initializes the contract in unpaused state. + */ + function __Pausable_init() internal initializer { + __Context_init_unchained(); + __Pausable_init_unchained(); + } + + function __Pausable_init_unchained() internal initializer { + _paused = false; + } + + /** + * @dev Returns true if the contract is paused, and false otherwise. + */ + function paused() public view virtual returns (bool) { + return _paused; + } + + /** + * @dev Modifier to make a function callable only when the contract is not paused. + * + * Requirements: + * + * - The contract must not be paused. + */ + modifier whenNotPaused() { + require(!paused(), "Pausable: paused"); + _; + } + + /** + * @dev Modifier to make a function callable only when the contract is paused. + * + * Requirements: + * + * - The contract must be paused. + */ + modifier whenPaused() { + require(paused(), "Pausable: not paused"); + _; + } + + /** + * @dev Triggers stopped state. + * + * Requirements: + * + * - The contract must not be paused. + */ + function _pause() internal virtual whenNotPaused { + _paused = true; + emit Paused(_msgSender()); + } + + /** + * @dev Returns to normal state. + * + * Requirements: + * + * - The contract must be paused. + */ + function _unpause() internal virtual whenPaused { + _paused = false; + emit Unpaused(_msgSender()); + } + uint256[49] private __gap; +} + + +// File contracts/security/Pausable.sol + + +pragma solidity 0.8.0; + + +/** + * @dev Contract module which allows children to implement an emergency stop + * mechanism that can be triggered by an authorized account. + * + * This module is used through inheritance. It will make available the + * modifiers `whenNotPaused` and `whenPaused`, which can be applied to + * the functions of your contract. Note that they will not be pausable by + * simply including this module, only once the modifiers are put in place. + */ +abstract contract Pausable is Initializable, PausableUpgradeable { + address private _pauser; + + event PauserChanged(address indexed previousPauser, address indexed newPauser); + + /** + * @dev The pausable constructor sets the original `pauser` of the contract to the sender + * account & Initializes the contract in unpaused state.. + */ + function __Pausable_init(address pauser) internal initializer { + require(pauser != address(0), "Pauser Address cannot be 0"); + __Pausable_init(); + _pauser = pauser; + } + + /** + * @return true if `msg.sender` is the owner of the contract. + */ + function isPauser(address pauser) public view returns (bool) { + return pauser == _pauser; + } + + /** + * @dev Throws if called by any account other than the pauser. + */ + modifier onlyPauser() { + require(isPauser(msg.sender), "Only pauser is allowed to perform this operation"); + _; + } + + /** + * @dev Allows the current pauser to transfer control of the contract to a newPauser. + * @param newPauser The address to transfer pauserShip to. + */ + function changePauser(address newPauser) public onlyPauser { + _changePauser(newPauser); + } + + /** + * @dev Transfers control of the contract to a newPauser. + * @param newPauser The address to transfer ownership to. + */ + function _changePauser(address newPauser) internal { + require(newPauser != address(0)); + emit PauserChanged(_pauser, newPauser); + _pauser = newPauser; + } + + function renouncePauser() external virtual onlyPauser { + emit PauserChanged(_pauser, address(0)); + _pauser = address(0); + } + + function pause() public onlyPauser { + _pause(); + } + + function unpause() public onlyPauser { + _unpause(); + } +} + + +// File contracts/hyphen/structures/LpTokenMetadata.sol + +pragma solidity 0.8.0; + +struct LpTokenMetadata { + address token; + uint256 suppliedLiquidity; + uint256 shares; +} + + +// File contracts/hyphen/interfaces/ILPToken.sol + +pragma solidity 0.8.0; + +interface ILPToken { + function approve(address to, uint256 tokenId) external; + + function balanceOf(address _owner) external view returns (uint256); + + function exists(uint256 _tokenId) external view returns (bool); + + function getAllNftIdsByUser(address _owner) external view returns (uint256[] memory); + + function getApproved(uint256 tokenId) external view returns (address); + + function initialize( + string memory _name, + string memory _symbol, + address _trustedForwarder + ) external; + + function isApprovedForAll(address _owner, address operator) external view returns (bool); + + function isTrustedForwarder(address forwarder) external view returns (bool); + + function liquidityPoolAddress() external view returns (address); + + function mint(address _to) external returns (uint256); + + function name() external view returns (string memory); + + function owner() external view returns (address); + + function ownerOf(uint256 tokenId) external view returns (address); + + function paused() external view returns (bool); + + function renounceOwnership() external; + + function safeTransferFrom( + address from, + address to, + uint256 tokenId + ) external; + + function safeTransferFrom( + address from, + address to, + uint256 tokenId, + bytes memory _data + ) external; + + function setApprovalForAll(address operator, bool approved) external; + + function setLiquidityPool(address _lpm) external; + + function setWhiteListPeriodManager(address _whiteListPeriodManager) external; + + function supportsInterface(bytes4 interfaceId) external view returns (bool); + + function symbol() external view returns (string memory); + + function tokenByIndex(uint256 index) external view returns (uint256); + + function tokenMetadata(uint256) + external + view + returns ( + address token, + uint256 totalSuppliedLiquidity, + uint256 totalShares + ); + + function tokenOfOwnerByIndex(address _owner, uint256 index) external view returns (uint256); + + function tokenURI(uint256 tokenId) external view returns (string memory); + + function totalSupply() external view returns (uint256); + + function transferFrom( + address from, + address to, + uint256 tokenId + ) external; + + function transferOwnership(address newOwner) external; + + function updateTokenMetadata(uint256 _tokenId, LpTokenMetadata memory _lpTokenMetadata) external; + + function whiteListPeriodManager() external view returns (address); +} + + +// File contracts/hyphen/structures/TokenConfig.sol + +pragma solidity 0.8.0; + +struct TokenInfo { + uint256 transferOverhead; + bool supportedToken; + uint256 equilibriumFee; // Percentage fee Represented in basis points + uint256 maxFee; // Percentage fee Represented in basis points + TokenConfig tokenConfig; +} + +struct TokenConfig { + uint256 min; + uint256 max; +} + + +// File contracts/hyphen/interfaces/ITokenManager.sol + +pragma solidity 0.8.0; + +interface ITokenManager { + function getEquilibriumFee(address tokenAddress) external view returns (uint256); + + function getMaxFee(address tokenAddress) external view returns (uint256); + + function changeFee( + address tokenAddress, + uint256 _equilibriumFee, + uint256 _maxFee + ) external; + + function tokensInfo(address tokenAddress) + external + view + returns ( + uint256 transferOverhead, + bool supportedToken, + uint256 equilibriumFee, + uint256 maxFee, + TokenConfig memory config + ); + + function getTokensInfo(address tokenAddress) external view returns (TokenInfo memory); + + function getDepositConfig(uint256 toChainId, address tokenAddress) external view returns (TokenConfig memory); + + function getTransferConfig(address tokenAddress) external view returns (TokenConfig memory); +} + + +// File contracts/hyphen/interfaces/IWhiteListPeriodManager.sol + +pragma solidity 0.8.0; + +interface IWhiteListPeriodManager { + function areWhiteListRestrictionsEnabled() external view returns (bool); + + function beforeLiquidityAddition( + address _lp, + address _token, + uint256 _amount + ) external; + + function beforeLiquidityRemoval( + address _lp, + address _token, + uint256 _amount + ) external; + + function beforeLiquidityTransfer( + address _from, + address _to, + address _token, + uint256 _amount + ) external; + + function getMaxCommunityLpPositon(address _token) external view returns (uint256); + + function initialize( + address _trustedForwarder, + address _liquidityProviders, + address _tokenManager + ) external; + + function isExcludedAddress(address) external view returns (bool); + + function isTrustedForwarder(address forwarder) external view returns (bool); + + function owner() external view returns (address); + + function paused() external view returns (bool); + + function perTokenTotalCap(address) external view returns (uint256); + + function perTokenWalletCap(address) external view returns (uint256); + + function renounceOwnership() external; + + function setAreWhiteListRestrictionsEnabled(bool _status) external; + + function setCap( + address _token, + uint256 _totalCap, + uint256 _perTokenWalletCap + ) external; + + function setCaps( + address[] memory _tokens, + uint256[] memory _totalCaps, + uint256[] memory _perTokenWalletCaps + ) external; + + function setIsExcludedAddressStatus(address[] memory _addresses, bool[] memory _status) external; + + function setLiquidityProviders(address _liquidityProviders) external; + + function setPerTokenWalletCap(address _token, uint256 _perTokenWalletCap) external; + + function setTokenManager(address _tokenManager) external; + + function setTotalCap(address _token, uint256 _totalCap) external; + + function transferOwnership(address newOwner) external; +} + + +// File contracts/hyphen/interfaces/ILiquidityPool.sol + +pragma solidity 0.8.0; + +interface ILiquidityPool { + function baseGas() external view returns (uint256); + + function changePauser(address newPauser) external; + + function checkHashStatus( + address tokenAddress, + uint256 amount, + address receiver, + bytes memory depositHash + ) external view returns (bytes32 hashSendTransaction, bool status); + + function depositConfig(uint256, address) external view returns (uint256 min, uint256 max); + + function depositErc20( + uint256 toChainId, + address tokenAddress, + address receiver, + uint256 amount, + string memory tag + ) external; + + function depositNative( + address receiver, + uint256 toChainId, + string memory tag + ) external; + + function gasFeeAccumulated(address, address) external view returns (uint256); + + function gasFeeAccumulatedByToken(address) external view returns (uint256); + + function getCurrentLiquidity(address tokenAddress) external view returns (uint256 currentLiquidity); + + function getExecutorManager() external view returns (address); + + function getRewardAmount(uint256 amount, address tokenAddress) external view returns (uint256 rewardAmount); + + function getTransferFee(address tokenAddress, uint256 amount) external view returns (uint256 fee); + + function incentivePool(address) external view returns (uint256); + + function initialize( + address _executorManagerAddress, + address pauser, + address _trustedForwarder, + address _tokenManager, + address _liquidityProviders + ) external; + + function isPauser(address pauser) external view returns (bool); + + function isTrustedForwarder(address forwarder) external view returns (bool); + + function owner() external view returns (address); + + function paused() external view returns (bool); + + function processedHash(bytes32) external view returns (bool); + + function renounceOwnership() external; + + function renouncePauser() external; + + function transfer(address _tokenAddress, address receiver, uint256 _tokenAmount) external; + + function sendFundsToUser( + address tokenAddress, + uint256 amount, + address receiver, + bytes memory depositHash, + uint256 tokenGasPrice, + uint256 fromChainId + ) external; + + function setBaseGas(uint128 gas) external; + + function setExecutorManager(address _executorManagerAddress) external; + + function setLiquidityProviders(address _liquidityProviders) external; + + function setTrustedForwarder(address trustedForwarder) external; + + function transferConfig(address) external view returns (uint256 min, uint256 max); + + function transferOwnership(address newOwner) external; + + function withdrawErc20GasFee(address tokenAddress) external; + + function withdrawNativeGasFee() external; +} + + +// File contracts/hyphen/LiquidityProviders.sol + +// $$\ $$\ $$\ $$\ $$\ $$\ $$$$$$$\ $$\ $$\ +// $$ | \__| \__| $$ |\__| $$ | $$ __$$\ \__| $$ | +// $$ | $$\ $$$$$$\ $$\ $$\ $$\ $$$$$$$ |$$\ $$$$$$\ $$\ $$\ $$ | $$ | $$$$$$\ $$$$$$\ $$\ $$\ $$\ $$$$$$$ | $$$$$$\ $$$$$$\ $$$$$$$\ +// $$ | $$ |$$ __$$\ $$ | $$ |$$ |$$ __$$ |$$ |\_$$ _| $$ | $$ | $$$$$$$ |$$ __$$\ $$ __$$\\$$\ $$ |$$ |$$ __$$ |$$ __$$\ $$ __$$\ $$ _____| +// $$ | $$ |$$ / $$ |$$ | $$ |$$ |$$ / $$ |$$ | $$ | $$ | $$ | $$ ____/ $$ | \__|$$ / $$ |\$$\$$ / $$ |$$ / $$ |$$$$$$$$ |$$ | \__|\$$$$$$\ +// $$ | $$ |$$ | $$ |$$ | $$ |$$ |$$ | $$ |$$ | $$ |$$\ $$ | $$ | $$ | $$ | $$ | $$ | \$$$ / $$ |$$ | $$ |$$ ____|$$ | \____$$\ +// $$$$$$$$\ $$ |\$$$$$$$ |\$$$$$$ |$$ |\$$$$$$$ |$$ | \$$$$ |\$$$$$$$ | $$ | $$ | \$$$$$$ | \$ / $$ |\$$$$$$$ |\$$$$$$$\ $$ | $$$$$$$ | +// \________|\__| \____$$ | \______/ \__| \_______|\__| \____/ \____$$ | \__| \__| \______/ \_/ \__| \_______| \_______|\__| \_______/ +// $$ | $$\ $$ | +// $$ | \$$$$$$ | +// \__| \______/ +pragma solidity 0.8.0; + +contract LiquidityProvidersOld is + Initializable, + ReentrancyGuardUpgradeable, + ERC2771ContextUpgradeable, + OwnableUpgradeable, + Pausable +{ + using SafeERC20Upgradeable for IERC20Upgradeable; + + address internal constant NATIVE = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; + uint256 public constant BASE_DIVISOR = 10**18; + + ILPToken internal lpToken; + ILiquidityPool internal liquidityPool; + ITokenManager internal tokenManager; + IWhiteListPeriodManager internal whiteListPeriodManager; + + event LiquidityAdded(address indexed tokenAddress, uint256 indexed amount, address indexed lp); + event LiquidityRemoved(address indexed tokenAddress, uint256 indexed amount, address indexed lp); + event FeeClaimed(address indexed tokenAddress, uint256 indexed fee, address indexed lp, uint256 sharesBurnt); + event FeeAdded(address indexed tokenAddress, uint256 indexed fee); + event EthReceived(address indexed sender, uint256 value); + event CurrentLiquidityChanged(address indexed token, uint256 indexed newValue); + + // LP Fee Distribution + mapping(address => uint256) public totalReserve; // Include Liquidity + Fee accumulated + mapping(address => uint256) public totalLiquidity; // Include Liquidity only + mapping(address => uint256) public currentLiquidity; // Include current liquidity, updated on every in and out transfer + mapping(address => uint256) public totalLPFees; + mapping(address => uint256) public totalSharesMinted; + + /** + * @dev Modifier for checking to validate a NFTId and it's ownership + * @param _tokenId token id to validate + * @param _transactor typically msgSender(), passed to verify against owner of _tokenId + */ + modifier onlyValidLpToken(uint256 _tokenId, address _transactor) { + (address token, , ) = lpToken.tokenMetadata(_tokenId); + require(lpToken.exists(_tokenId), "ERR__TOKEN_DOES_NOT_EXIST"); + require(lpToken.ownerOf(_tokenId) == _transactor, "ERR__TRANSACTOR_DOES_NOT_OWN_NFT"); + _; + } + + /** + * @dev Modifier for checking if msg.sender in liquiditypool + */ + modifier onlyLiquidityPool() { + require(_msgSender() == address(liquidityPool), "ERR__UNAUTHORIZED"); + _; + } + + modifier tokenChecks(address tokenAddress) { + require(tokenAddress != address(0), "Token address cannot be 0"); + require(_isSupportedToken(tokenAddress), "Token not supported"); + _; + } + + /** + * @dev initalizes the contract, acts as constructor + * @param _trustedForwarder address of trusted forwarder + */ + function initialize( + address _trustedForwarder, + address _lpToken, + address _tokenManager, + address _pauser + ) public initializer { + __ERC2771Context_init(_trustedForwarder); + __Ownable_init(); + __Pausable_init(_pauser); + __ReentrancyGuard_init(); + _setLPToken(_lpToken); + _setTokenManager(_tokenManager); + } + + function _isSupportedToken(address _token) internal view returns (bool) { + return tokenManager.getTokensInfo(_token).supportedToken; + } + + function getTotalReserveByToken(address tokenAddress) public view returns (uint256) { + return totalReserve[tokenAddress]; + } + + function getSuppliedLiquidityByToken(address tokenAddress) public view returns (uint256) { + return totalLiquidity[tokenAddress]; + } + + function getTotalLPFeeByToken(address tokenAddress) public view returns (uint256) { + return totalLPFees[tokenAddress]; + } + + function getCurrentLiquidity(address tokenAddress) public view returns (uint256) { + return currentLiquidity[tokenAddress]; + } + + /** + * @dev To be called post initialization, used to set address of NFT Contract + * @param _lpToken address of lpToken + */ + function setLpToken(address _lpToken) external onlyOwner { + _setLPToken(_lpToken); + } + + /** + * Internal method to set LP token contract. + */ + function _setLPToken(address _lpToken) internal { + lpToken = ILPToken(_lpToken); + } + + function increaseCurrentLiquidity(address tokenAddress, uint256 amount) public onlyLiquidityPool { + _increaseCurrentLiquidity(tokenAddress, amount); + } + + function decreaseCurrentLiquidity(address tokenAddress, uint256 amount) public onlyLiquidityPool { + _decreaseCurrentLiquidity(tokenAddress, amount); + } + + function _increaseCurrentLiquidity(address tokenAddress, uint256 amount) private { + currentLiquidity[tokenAddress] += amount; + emit CurrentLiquidityChanged(tokenAddress, currentLiquidity[tokenAddress]); + } + + function _decreaseCurrentLiquidity(address tokenAddress, uint256 amount) private { + currentLiquidity[tokenAddress] -= amount; + emit CurrentLiquidityChanged(tokenAddress, currentLiquidity[tokenAddress]); + } + + /** + * Public method to set TokenManager contract. + */ + function setTokenManager(address _tokenManager) external onlyOwner { + _setTokenManager(_tokenManager); + } + + /** + * Internal method to set TokenManager contract. + */ + function _setTokenManager(address _tokenManager) internal { + tokenManager = ITokenManager(_tokenManager); + } + + /** + * @dev To be called post initialization, used to set address of WhiteListPeriodManager Contract + * @param _whiteListPeriodManager address of WhiteListPeriodManager + */ + function setWhiteListPeriodManager(address _whiteListPeriodManager) external onlyOwner { + whiteListPeriodManager = IWhiteListPeriodManager(_whiteListPeriodManager); + } + + /** + * @dev To be called post initialization, used to set address of LiquidityPool Contract + * @param _liquidityPool address of LiquidityPool + */ + function setLiquidityPool(address _liquidityPool) external onlyOwner { + liquidityPool = ILiquidityPool(_liquidityPool); + } + + /** + * @dev Returns price of Base token in terms of LP Shares + * @param _baseToken address of baseToken + * @return Price of Base token in terms of LP Shares + */ + function getTokenPriceInLPShares(address _baseToken) public view returns (uint256) { + uint256 supply = totalSharesMinted[_baseToken]; + if (supply > 0) { + return totalSharesMinted[_baseToken] / totalReserve[_baseToken]; + } + return BASE_DIVISOR; + } + + /** + * @dev Converts shares to token amount + */ + + function sharesToTokenAmount(uint256 _shares, address _tokenAddress) public view returns (uint256) { + return (_shares * totalReserve[_tokenAddress]) / totalSharesMinted[_tokenAddress]; + } + + /** + * @dev Returns the fee accumulated on a given NFT + * @param _nftId Id of NFT + * @return accumulated fee + */ + function getFeeAccumulatedOnNft(uint256 _nftId) public view returns (uint256) { + require(lpToken.exists(_nftId), "ERR__INVALID_NFT"); + + (address _tokenAddress, uint256 nftSuppliedLiquidity, uint256 totalNFTShares) = lpToken.tokenMetadata(_nftId); + + if (totalNFTShares == 0) { + return 0; + } + // Calculate rewards accumulated + uint256 eligibleLiquidity = sharesToTokenAmount(totalNFTShares, _tokenAddress); + uint256 lpFeeAccumulated; + + // Handle edge cases where eligibleLiquidity is less than what was supplied by very small amount + if (nftSuppliedLiquidity > eligibleLiquidity) { + lpFeeAccumulated = 0; + } else { + unchecked { + lpFeeAccumulated = eligibleLiquidity - nftSuppliedLiquidity; + } + } + return lpFeeAccumulated; + } + + /** + * @dev Records fee being added to total reserve + * @param _token Address of Token for which LP fee is being added + * @param _amount Amount being added + */ + function addLPFee(address _token, uint256 _amount) external onlyLiquidityPool tokenChecks(_token) whenNotPaused { + totalReserve[_token] += _amount; + totalLPFees[_token] += _amount; + emit FeeAdded(_token, _amount); + } + + /** + * @dev Internal function to add liquidity to a new NFT + */ + function _addLiquidity(address _token, uint256 _amount) internal { + require(_amount > 0, "ERR__AMOUNT_IS_0"); + uint256 nftId = lpToken.mint(_msgSender()); + LpTokenMetadata memory data = LpTokenMetadata(_token, 0, 0); + lpToken.updateTokenMetadata(nftId, data); + _increaseLiquidity(nftId, _amount); + } + + /** + * @dev Function to mint a new NFT for a user, add native liquidity and store the + * record in the newly minted NFT + */ + function addNativeLiquidity() external payable nonReentrant tokenChecks(NATIVE) whenNotPaused { + (bool success, ) = address(liquidityPool).call{value: msg.value}(""); + require(success, "ERR__NATIVE_TRANSFER_FAILED"); + _addLiquidity(NATIVE, msg.value); + } + + /** + * @dev Function to mint a new NFT for a user, add token liquidity and store the + * record in the newly minted NFT + * @param _token Address of token for which liquidity is to be added + * @param _amount Amount of liquidity added + */ + function addTokenLiquidity(address _token, uint256 _amount) + external + nonReentrant + tokenChecks(_token) + whenNotPaused + { + require(_token != NATIVE, "ERR__WRONG_FUNCTION"); + require( + IERC20Upgradeable(_token).allowance(_msgSender(), address(this)) >= _amount, + "ERR__INSUFFICIENT_ALLOWANCE" + ); + SafeERC20Upgradeable.safeTransferFrom(IERC20Upgradeable(_token), _msgSender(), address(liquidityPool), _amount); + _addLiquidity(_token, _amount); + } + + /** + * @dev Internal helper function to increase liquidity in a given NFT + */ + function _increaseLiquidity(uint256 _nftId, uint256 _amount) internal onlyValidLpToken(_nftId, _msgSender()) { + (address token, uint256 totalSuppliedLiquidity, uint256 totalShares) = lpToken.tokenMetadata(_nftId); + + require(_amount != 0, "ERR__AMOUNT_IS_0"); + whiteListPeriodManager.beforeLiquidityAddition(_msgSender(), token, _amount); + + uint256 mintedSharesAmount; + // Adding liquidity in the pool for the first time + if (totalReserve[token] == 0) { + mintedSharesAmount = BASE_DIVISOR * _amount; + } else { + mintedSharesAmount = (_amount * totalSharesMinted[token]) / totalReserve[token]; + } + + require(mintedSharesAmount >= BASE_DIVISOR, "ERR__AMOUNT_BELOW_MIN_LIQUIDITY"); + + totalLiquidity[token] += _amount; + totalReserve[token] += _amount; + totalSharesMinted[token] += mintedSharesAmount; + + LpTokenMetadata memory data = LpTokenMetadata( + token, + totalSuppliedLiquidity + _amount, + totalShares + mintedSharesAmount + ); + lpToken.updateTokenMetadata(_nftId, data); + + // Increase the current liquidity + _increaseCurrentLiquidity(token, _amount); + emit LiquidityAdded(token, _amount, _msgSender()); + } + + /** + * @dev Function to allow LPs to add ERC20 token liquidity to existing NFT + * @param _nftId ID of NFT for updating the balances + * @param _amount Token amount to be added + */ + function increaseTokenLiquidity(uint256 _nftId, uint256 _amount) external nonReentrant whenNotPaused { + (address token, , ) = lpToken.tokenMetadata(_nftId); + require(_isSupportedToken(token), "ERR__TOKEN_NOT_SUPPORTED"); + require(token != NATIVE, "ERR__WRONG_FUNCTION"); + require( + IERC20Upgradeable(token).allowance(_msgSender(), address(this)) >= _amount, + "ERR__INSUFFICIENT_ALLOWANCE" + ); + SafeERC20Upgradeable.safeTransferFrom(IERC20Upgradeable(token), _msgSender(), address(liquidityPool), _amount); + _increaseLiquidity(_nftId, _amount); + } + + /** + * @dev Function to allow LPs to add native token liquidity to existing NFT + */ + function increaseNativeLiquidity(uint256 _nftId) external payable nonReentrant whenNotPaused { + (address token, , ) = lpToken.tokenMetadata(_nftId); + require(_isSupportedToken(NATIVE), "ERR__TOKEN_NOT_SUPPORTED"); + require(token == NATIVE, "ERR__WRONG_FUNCTION"); + (bool success, ) = address(liquidityPool).call{value: msg.value}(""); + require(success, "ERR__NATIVE_TRANSFER_FAILED"); + _increaseLiquidity(_nftId, msg.value); + } + + /** + * @dev Function to allow LPs to remove their liquidity from an existing NFT + * Also automatically redeems any earned fee + */ + function removeLiquidity(uint256 _nftId, uint256 _amount) + external + nonReentrant + onlyValidLpToken(_nftId, _msgSender()) + whenNotPaused + { + (address _tokenAddress, uint256 nftSuppliedLiquidity, uint256 totalNFTShares) = lpToken.tokenMetadata(_nftId); + require(_isSupportedToken(_tokenAddress), "ERR__TOKEN_NOT_SUPPORTED"); + + require(_amount != 0, "ERR__INVALID_AMOUNT"); + require(nftSuppliedLiquidity >= _amount, "ERR__INSUFFICIENT_LIQUIDITY"); + whiteListPeriodManager.beforeLiquidityRemoval(_msgSender(), _tokenAddress, _amount); + // Claculate how much shares represent input amount + uint256 lpSharesForInputAmount = _amount * getTokenPriceInLPShares(_tokenAddress); + + // Calculate rewards accumulated + uint256 eligibleLiquidity = sharesToTokenAmount(totalNFTShares, _tokenAddress); + + uint256 lpFeeAccumulated; + + // Handle edge cases where eligibleLiquidity is less than what was supplied by very small amount + if (nftSuppliedLiquidity > eligibleLiquidity) { + lpFeeAccumulated = 0; + } else { + unchecked { + lpFeeAccumulated = eligibleLiquidity - nftSuppliedLiquidity; + } + } + // Calculate amount of lp shares that represent accumulated Fee + uint256 lpSharesRepresentingFee = lpFeeAccumulated * getTokenPriceInLPShares(_tokenAddress); + + totalLPFees[_tokenAddress] -= lpFeeAccumulated; + uint256 amountToWithdraw = _amount + lpFeeAccumulated; + uint256 lpSharesToBurn = lpSharesForInputAmount + lpSharesRepresentingFee; + + // Handle round off errors to avoid dust lp token in contract + if (totalNFTShares - lpSharesToBurn < BASE_DIVISOR) { + lpSharesToBurn = totalNFTShares; + } + totalReserve[_tokenAddress] -= amountToWithdraw; + totalLiquidity[_tokenAddress] -= _amount; + totalSharesMinted[_tokenAddress] -= lpSharesToBurn; + + _decreaseCurrentLiquidity(_tokenAddress, _amount); + + _burnSharesFromNft(_nftId, lpSharesToBurn, _amount, _tokenAddress); + + _transferFromLiquidityPool(_tokenAddress, _msgSender(), amountToWithdraw); + + emit LiquidityRemoved(_tokenAddress, amountToWithdraw, _msgSender()); + } + + /** + * @dev Function to allow LPs to claim the fee earned on their NFT + * @param _nftId ID of NFT where liquidity is recorded + */ + function claimFee(uint256 _nftId) external onlyValidLpToken(_nftId, _msgSender()) whenNotPaused nonReentrant { + (address _tokenAddress, uint256 nftSuppliedLiquidity, uint256 totalNFTShares) = lpToken.tokenMetadata(_nftId); + require(_isSupportedToken(_tokenAddress), "ERR__TOKEN_NOT_SUPPORTED"); + + uint256 lpSharesForSuppliedLiquidity = nftSuppliedLiquidity * getTokenPriceInLPShares(_tokenAddress); + + // Calculate rewards accumulated + uint256 eligibleLiquidity = sharesToTokenAmount(totalNFTShares, _tokenAddress); + uint256 lpFeeAccumulated = eligibleLiquidity - nftSuppliedLiquidity; + require(lpFeeAccumulated > 0, "ERR__NO_REWARDS_TO_CLAIM"); + // Calculate amount of lp shares that represent accumulated Fee + uint256 lpSharesRepresentingFee = totalNFTShares - lpSharesForSuppliedLiquidity; + + totalReserve[_tokenAddress] -= lpFeeAccumulated; + totalSharesMinted[_tokenAddress] -= lpSharesRepresentingFee; + totalLPFees[_tokenAddress] -= lpFeeAccumulated; + + _burnSharesFromNft(_nftId, lpSharesRepresentingFee, 0, _tokenAddress); + _transferFromLiquidityPool(_tokenAddress, _msgSender(), lpFeeAccumulated); + emit FeeClaimed(_tokenAddress, lpFeeAccumulated, _msgSender(), lpSharesRepresentingFee); + } + + /** + * @dev Internal Function to burn LP shares and remove liquidity from existing NFT + */ + function _burnSharesFromNft( + uint256 _nftId, + uint256 _shares, + uint256 _tokenAmount, + address _tokenAddress + ) internal { + (, uint256 nftSuppliedLiquidity, uint256 nftShares) = lpToken.tokenMetadata(_nftId); + nftShares -= _shares; + nftSuppliedLiquidity -= _tokenAmount; + + lpToken.updateTokenMetadata(_nftId, LpTokenMetadata(_tokenAddress, nftSuppliedLiquidity, nftShares)); + } + + function _transferFromLiquidityPool( + address _tokenAddress, + address _receiver, + uint256 _tokenAmount + ) internal { + liquidityPool.transfer(_tokenAddress, _receiver, _tokenAmount); + } + + function getSuppliedLiquidity(uint256 _nftId) external view returns (uint256) { + (, uint256 totalSuppliedLiquidity, ) = lpToken.tokenMetadata(_nftId); + return totalSuppliedLiquidity; + } + + function _msgSender() + internal + view + virtual + override(ContextUpgradeable, ERC2771ContextUpgradeable) + returns (address sender) + { + return ERC2771ContextUpgradeable._msgSender(); + } + + function _msgData() + internal + view + virtual + override(ContextUpgradeable, ERC2771ContextUpgradeable) + returns (bytes calldata) + { + return ERC2771ContextUpgradeable._msgData(); + } + + receive() external payable { + emit EthReceived(_msgSender(), msg.value); + } +} diff --git a/contracts/test/previous-release/TokenManager.sol b/contracts/test/previous-release/TokenManager.sol new file mode 100644 index 0000000..6a4af8b --- /dev/null +++ b/contracts/test/previous-release/TokenManager.sol @@ -0,0 +1,453 @@ +// Sources flattened with hardhat v2.8.4 https://hardhat.org + +// File @openzeppelin/contracts/utils/Context.sol@v4.3.0 + + +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; + } +} + + +// File @openzeppelin/contracts/access/Ownable.sol@v4.3.0 + + +pragma solidity ^0.8.0; + +/** + * @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() { + _setOwner(_msgSender()); + } + + /** + * @dev Returns the address of the current owner. + */ + function owner() public view virtual returns (address) { + return _owner; + } + + /** + * @dev Throws if called by any account other than the owner. + */ + modifier onlyOwner() { + require(owner() == _msgSender(), "Ownable: caller is not the owner"); + _; + } + + /** + * @dev Leaves the contract without owner. It will not be possible to call + * `onlyOwner` functions anymore. Can only be called by the current owner. + * + * NOTE: Renouncing ownership will leave the contract without an owner, + * thereby removing any functionality that is only available to the owner. + */ + function renounceOwnership() public virtual onlyOwner { + _setOwner(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"); + _setOwner(newOwner); + } + + function _setOwner(address newOwner) private { + address oldOwner = _owner; + _owner = newOwner; + emit OwnershipTransferred(oldOwner, newOwner); + } +} + + +// File @openzeppelin/contracts/security/Pausable.sol@v4.3.0 + + +pragma solidity ^0.8.0; + +/** + * @dev Contract module which allows children to implement an emergency stop + * mechanism that can be triggered by an authorized account. + * + * This module is used through inheritance. It will make available the + * modifiers `whenNotPaused` and `whenPaused`, which can be applied to + * the functions of your contract. Note that they will not be pausable by + * simply including this module, only once the modifiers are put in place. + */ +abstract contract Pausable is Context { + /** + * @dev Emitted when the pause is triggered by `account`. + */ + event Paused(address account); + + /** + * @dev Emitted when the pause is lifted by `account`. + */ + event Unpaused(address account); + + bool private _paused; + + /** + * @dev Initializes the contract in unpaused state. + */ + constructor() { + _paused = false; + } + + /** + * @dev Returns true if the contract is paused, and false otherwise. + */ + function paused() public view virtual returns (bool) { + return _paused; + } + + /** + * @dev Modifier to make a function callable only when the contract is not paused. + * + * Requirements: + * + * - The contract must not be paused. + */ + modifier whenNotPaused() { + require(!paused(), "Pausable: paused"); + _; + } + + /** + * @dev Modifier to make a function callable only when the contract is paused. + * + * Requirements: + * + * - The contract must be paused. + */ + modifier whenPaused() { + require(paused(), "Pausable: not paused"); + _; + } + + /** + * @dev Triggers stopped state. + * + * Requirements: + * + * - The contract must not be paused. + */ + function _pause() internal virtual whenNotPaused { + _paused = true; + emit Paused(_msgSender()); + } + + /** + * @dev Returns to normal state. + * + * Requirements: + * + * - The contract must be paused. + */ + function _unpause() internal virtual whenPaused { + _paused = false; + emit Unpaused(_msgSender()); + } +} + + +// File contracts/hyphen/metatx/ERC2771Context.sol + + +pragma solidity 0.8.0; + +/** + * @dev Context variant with ERC2771 support. + */ +abstract contract ERC2771Context is Context { + address internal _trustedForwarder; + + constructor(address trustedForwarder) { + _trustedForwarder = trustedForwarder; + } + + function isTrustedForwarder(address forwarder) public view virtual returns (bool) { + return forwarder == _trustedForwarder; + } + + function _msgSender() internal view virtual override returns (address sender) { + if (isTrustedForwarder(msg.sender)) { + // The assembly code is more direct than the Solidity version using `abi.decode`. + assembly { + sender := shr(96, calldataload(sub(calldatasize(), 20))) + } + } else { + return super._msgSender(); + } + } + + function _msgData() internal view virtual override returns (bytes calldata) { + if (isTrustedForwarder(msg.sender)) { + return msg.data[:msg.data.length - 20]; + } else { + return super._msgData(); + } + } +} + + +// File contracts/hyphen/structures/TokenConfig.sol + +pragma solidity 0.8.0; + +struct TokenInfo { + uint256 transferOverhead; + bool supportedToken; + uint256 equilibriumFee; // Percentage fee Represented in basis points + uint256 maxFee; // Percentage fee Represented in basis points + TokenConfig tokenConfig; +} + +struct TokenConfig { + uint256 min; + uint256 max; +} + + +// File contracts/hyphen/interfaces/ITokenManager.sol + +pragma solidity 0.8.0; + +interface ITokenManager { + function getEquilibriumFee(address tokenAddress) external view returns (uint256); + + function getMaxFee(address tokenAddress) external view returns (uint256); + + function changeFee( + address tokenAddress, + uint256 _equilibriumFee, + uint256 _maxFee + ) external; + + function tokensInfo(address tokenAddress) + external + view + returns ( + uint256 transferOverhead, + bool supportedToken, + uint256 equilibriumFee, + uint256 maxFee, + TokenConfig memory config + ); + + function getTokensInfo(address tokenAddress) external view returns (TokenInfo memory); + + function getDepositConfig(uint256 toChainId, address tokenAddress) external view returns (TokenConfig memory); + + function getTransferConfig(address tokenAddress) external view returns (TokenConfig memory); +} + + +// File contracts/hyphen/token/TokenManager.sol + +// $$$$$$$$\ $$\ $$\ $$\ +// \__$$ __| $$ | $$$\ $$$ | +// $$ | $$$$$$\ $$ | $$\ $$$$$$\ $$$$$$$\ $$$$\ $$$$ | $$$$$$\ $$$$$$$\ $$$$$$\ $$$$$$\ $$$$$$\ $$$$$$\ +// $$ |$$ __$$\ $$ | $$ |$$ __$$\ $$ __$$\ $$\$$\$$ $$ | \____$$\ $$ __$$\ \____$$\ $$ __$$\ $$ __$$\ $$ __$$\ +// $$ |$$ / $$ |$$$$$$ / $$$$$$$$ |$$ | $$ | $$ \$$$ $$ | $$$$$$$ |$$ | $$ | $$$$$$$ |$$ / $$ |$$$$$$$$ |$$ | \__| +// $$ |$$ | $$ |$$ _$$< $$ ____|$$ | $$ | $$ |\$ /$$ |$$ __$$ |$$ | $$ |$$ __$$ |$$ | $$ |$$ ____|$$ | +// $$ |\$$$$$$ |$$ | \$$\ \$$$$$$$\ $$ | $$ | $$ | \_/ $$ |\$$$$$$$ |$$ | $$ |\$$$$$$$ |\$$$$$$$ |\$$$$$$$\ $$ | +// \__| \______/ \__| \__| \_______|\__| \__| \__| \__| \_______|\__| \__| \_______| \____$$ | \_______|\__| +// $$\ $$ | +// \$$$$$$ | +// \______/ + +pragma solidity 0.8.0; + + + + +contract TokenManagerOld is ITokenManager, ERC2771Context, Ownable, Pausable { + mapping(address => TokenInfo) public override tokensInfo; + + event FeeChanged(address indexed tokenAddress, uint256 indexed equilibriumFee, uint256 indexed maxFee); + + modifier tokenChecks(address tokenAddress) { + require(tokenAddress != address(0), "Token address cannot be 0"); + require(tokensInfo[tokenAddress].supportedToken, "Token not supported"); + + _; + } + + /** + * First key is toChainId and second key is token address being deposited on current chain + */ + mapping(uint256 => mapping(address => TokenConfig)) public depositConfig; + + /** + * Store min/max amount of token to transfer based on token address + */ + mapping(address => TokenConfig) public transferConfig; + + constructor(address trustedForwarder) ERC2771Context(trustedForwarder) Ownable() Pausable() { + // Empty Constructor + } + + function getEquilibriumFee(address tokenAddress) public view override returns (uint256) { + return tokensInfo[tokenAddress].equilibriumFee; + } + + function getMaxFee(address tokenAddress) public view override returns (uint256) { + return tokensInfo[tokenAddress].maxFee; + } + + function changeFee( + address tokenAddress, + uint256 _equilibriumFee, + uint256 _maxFee + ) external override onlyOwner whenNotPaused { + require(_equilibriumFee != 0, "Equilibrium Fee cannot be 0"); + require(_maxFee != 0, "Max Fee cannot be 0"); + tokensInfo[tokenAddress].equilibriumFee = _equilibriumFee; + tokensInfo[tokenAddress].maxFee = _maxFee; + emit FeeChanged(tokenAddress, tokensInfo[tokenAddress].equilibriumFee, tokensInfo[tokenAddress].maxFee); + } + + function setTokenTransferOverhead(address tokenAddress, uint256 gasOverhead) + external + tokenChecks(tokenAddress) + onlyOwner + { + tokensInfo[tokenAddress].transferOverhead = gasOverhead; + } + + /** + * Set DepositConfig for the given combination of toChainId, tokenAddress. + * This is used while depositing token in Liquidity Pool. Based on the destination chainid + * min and max deposit amount is checked. + */ + function setDepositConfig( + uint256[] memory toChainId, + address[] memory tokenAddresses, + TokenConfig[] memory tokenConfig + ) external onlyOwner { + require( + (toChainId.length == tokenAddresses.length) && (tokenAddresses.length == tokenConfig.length), + " ERR_ARRAY_LENGTH_MISMATCH" + ); + uint256 length = tokenConfig.length; + for (uint256 index; index < length; ) { + depositConfig[toChainId[index]][tokenAddresses[index]].min = tokenConfig[index].min; + depositConfig[toChainId[index]][tokenAddresses[index]].max = tokenConfig[index].max; + unchecked { + ++index; + } + } + } + + function addSupportedToken( + address tokenAddress, + uint256 minCapLimit, + uint256 maxCapLimit, + uint256 equilibriumFee, + uint256 maxFee, + uint256 transferOverhead + ) external onlyOwner { + require(tokenAddress != address(0), "Token address cannot be 0"); + require(maxCapLimit > minCapLimit, "maxCapLimit > minCapLimit"); + tokensInfo[tokenAddress].supportedToken = true; + transferConfig[tokenAddress].min = minCapLimit; + transferConfig[tokenAddress].max = maxCapLimit; + tokensInfo[tokenAddress].tokenConfig = transferConfig[tokenAddress]; + tokensInfo[tokenAddress].equilibriumFee = equilibriumFee; + tokensInfo[tokenAddress].maxFee = maxFee; + tokensInfo[tokenAddress].transferOverhead = transferOverhead; + } + + function removeSupportedToken(address tokenAddress) external tokenChecks(tokenAddress) onlyOwner { + tokensInfo[tokenAddress].supportedToken = false; + } + + function updateTokenCap( + address tokenAddress, + uint256 minCapLimit, + uint256 maxCapLimit + ) external tokenChecks(tokenAddress) onlyOwner { + require(maxCapLimit > minCapLimit, "maxCapLimit > minCapLimit"); + transferConfig[tokenAddress].min = minCapLimit; + transferConfig[tokenAddress].max = maxCapLimit; + } + + function getTokensInfo(address tokenAddress) public view override returns (TokenInfo memory) { + TokenInfo memory tokenInfo = TokenInfo( + tokensInfo[tokenAddress].transferOverhead, + tokensInfo[tokenAddress].supportedToken, + tokensInfo[tokenAddress].equilibriumFee, + tokensInfo[tokenAddress].maxFee, + transferConfig[tokenAddress] + ); + return tokenInfo; + } + + function getDepositConfig(uint256 toChainId, address tokenAddress) + public + view + override + returns (TokenConfig memory) + { + return depositConfig[toChainId][tokenAddress]; + } + + function getTransferConfig(address tokenAddress) public view override returns (TokenConfig memory) { + return transferConfig[tokenAddress]; + } + + function _msgSender() internal view virtual override(Context, ERC2771Context) returns (address sender) { + return ERC2771Context._msgSender(); + } + + function _msgData() internal view virtual override(Context, ERC2771Context) returns (bytes calldata) { + return ERC2771Context._msgData(); + } + + function pause() external onlyOwner { + _pause(); + } + + function unpause() external onlyOwner { + _unpause(); + } +} diff --git a/scripts/deploy-avalanche.ts b/scripts/deploy-avalanche.ts index d79b334..7da1f93 100644 --- a/scripts/deploy-avalanche.ts +++ b/scripts/deploy-avalanche.ts @@ -36,6 +36,7 @@ import { deploy, IDeployConfig } from "./helpers"; decimals: 6, rewardTokenAddress: "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", rewardRatePerSecond: parseUnits("0.000034814379098636", 18), + excessStateTransferFeePer: parseUnits("0.045", 8), }, // WETH { @@ -65,6 +66,7 @@ import { deploy, IDeployConfig } from "./helpers"; decimals: 18, rewardTokenAddress: "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", rewardRatePerSecond: parseUnits("0.000030666706752555", 18), + excessStateTransferFeePer: parseUnits("0.045", 8), }, ], }; diff --git a/scripts/deploy-ethereum.ts b/scripts/deploy-ethereum.ts index 7732c67..393018c 100644 --- a/scripts/deploy-ethereum.ts +++ b/scripts/deploy-ethereum.ts @@ -30,6 +30,7 @@ import { deploy, IDeployConfig } from "./helpers"; decimals: 6, rewardTokenAddress: "0xf17e65822b568b3903685a7c9f496cf7656cc6c2", rewardRatePerSecond: parseUnits("0.002247835648148150", 18), + excessStateTransferFeePer: parseUnits("0.045", 8), }, // USDC { @@ -59,6 +60,7 @@ import { deploy, IDeployConfig } from "./helpers"; decimals: 6, rewardTokenAddress: "0xf17e65822b568b3903685a7c9f496cf7656cc6c2", rewardRatePerSecond: parseUnits("0.006383761574074080", 18), + excessStateTransferFeePer: parseUnits("0.045", 8), }, // BICO { @@ -82,6 +84,7 @@ import { deploy, IDeployConfig } from "./helpers"; decimals: 18, rewardTokenAddress: "0xf17e65822b568b3903685a7c9f496cf7656cc6c2", rewardRatePerSecond: parseUnits("0.002796886574074070", 18), + excessStateTransferFeePer: parseUnits("0.045", 8), }, // ETH { @@ -111,6 +114,7 @@ import { deploy, IDeployConfig } from "./helpers"; decimals: 18, rewardTokenAddress: "0xf17e65822b568b3903685a7c9f496cf7656cc6c2", rewardRatePerSecond: parseUnits("0.017809687500000000", 18), + excessStateTransferFeePer: parseUnits("0.045", 8), }, ], }; diff --git a/scripts/deploy-fuji.ts b/scripts/deploy-fuji.ts index e051353..0a89ec8 100644 --- a/scripts/deploy-fuji.ts +++ b/scripts/deploy-fuji.ts @@ -1,4 +1,5 @@ import { ethers } from "hardhat"; +import { parseUnits } from "ethers/lib/utils"; import { deploy, IDeployConfig } from "./helpers"; (async () => { @@ -34,6 +35,7 @@ import { deploy, IDeployConfig } from "./helpers"; decimals: 18, rewardRatePerSecond: 100, rewardTokenAddress: "0xB4E0F6FEF81BdFea0856bB846789985c9CFf7e85", + excessStateTransferFeePer: parseUnits("0.045", 8) }, { tokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", @@ -62,6 +64,7 @@ import { deploy, IDeployConfig } from "./helpers"; decimals: 18, rewardRatePerSecond: 100, rewardTokenAddress: "0xB4E0F6FEF81BdFea0856bB846789985c9CFf7e85", + excessStateTransferFeePer: parseUnits("0.045", 8) }, ], }; diff --git a/scripts/deploy-goerli.ts b/scripts/deploy-goerli.ts index 9d192e3..7f390d3 100644 --- a/scripts/deploy-goerli.ts +++ b/scripts/deploy-goerli.ts @@ -1,4 +1,5 @@ import { ethers } from "hardhat"; +import { parseUnits } from "ethers/lib/utils"; import { deploy, IDeployConfig } from "./helpers"; (async () => { @@ -34,6 +35,7 @@ import { deploy, IDeployConfig } from "./helpers"; decimals: 18, rewardRatePerSecond: 100, rewardTokenAddress: "0x64ef393b6846114bad71e2cb2ccc3e10736b5716", + excessStateTransferFeePer: parseUnits("0.045", 8), }, { tokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", @@ -62,6 +64,7 @@ import { deploy, IDeployConfig } from "./helpers"; decimals: 18, rewardRatePerSecond: 100, rewardTokenAddress: "0x64ef393b6846114bad71e2cb2ccc3e10736b5716", + excessStateTransferFeePer: parseUnits("0.045", 8), }, { tokenAddress: "0xb5B640E6414b6DeF4FC9B3C1EeF373925effeCcF", @@ -84,6 +87,7 @@ import { deploy, IDeployConfig } from "./helpers"; decimals: 6, rewardRatePerSecond: 100, rewardTokenAddress: "0x64ef393b6846114bad71e2cb2ccc3e10736b5716", + excessStateTransferFeePer: parseUnits("0.045", 8), }, ], }; diff --git a/scripts/deploy-mumbai.ts b/scripts/deploy-mumbai.ts index f874426..63a85a6 100644 --- a/scripts/deploy-mumbai.ts +++ b/scripts/deploy-mumbai.ts @@ -1,5 +1,6 @@ import { ethers } from "hardhat"; import { deploy, IDeployConfig } from "./helpers"; +import { parseUnits } from "ethers/lib/utils"; (async () => { const config: IDeployConfig = { @@ -34,6 +35,7 @@ import { deploy, IDeployConfig } from "./helpers"; decimals: 18, rewardRatePerSecond: 100, rewardTokenAddress: "0xeabc4b91d9375796aa4f69cc764a4ab509080a58", + excessStateTransferFeePer: parseUnits("0.045", 8), }, { tokenAddress: "0xa6fa4fb5f76172d178d61b04b0ecd319c5d1c0aa", @@ -62,6 +64,7 @@ import { deploy, IDeployConfig } from "./helpers"; decimals: 18, rewardRatePerSecond: 100, rewardTokenAddress: "0xeabc4b91d9375796aa4f69cc764a4ab509080a58", + excessStateTransferFeePer: parseUnits("0.045", 8), }, { tokenAddress: "0xdA5289fCAAF71d52a80A254da614a192b693e977", @@ -84,6 +87,7 @@ import { deploy, IDeployConfig } from "./helpers"; decimals: 6, rewardRatePerSecond: 100, rewardTokenAddress: "0xeabc4b91d9375796aa4f69cc764a4ab509080a58", + excessStateTransferFeePer: parseUnits("0.045", 8), }, ], }; diff --git a/scripts/deploy-polygon.ts b/scripts/deploy-polygon.ts index 0ec4809..69612c7 100644 --- a/scripts/deploy-polygon.ts +++ b/scripts/deploy-polygon.ts @@ -30,6 +30,7 @@ import { deploy, IDeployConfig } from "./helpers"; decimals: 6, rewardTokenAddress: "0x91c89A94567980f0e9723b487b0beD586eE96aa7", rewardRatePerSecond: parseUnits("0.000723090277777778", 18), + excessStateTransferFeePer: parseUnits("0.045", 8), }, // USDC { @@ -59,6 +60,7 @@ import { deploy, IDeployConfig } from "./helpers"; decimals: 6, rewardTokenAddress: "0x91c89A94567980f0e9723b487b0beD586eE96aa7", rewardRatePerSecond: parseUnits("0.003529664351851850", 18), + excessStateTransferFeePer: parseUnits("0.045", 8), }, // BICO { @@ -82,6 +84,7 @@ import { deploy, IDeployConfig } from "./helpers"; decimals: 18, rewardTokenAddress: "0x91c89A94567980f0e9723b487b0beD586eE96aa7", rewardRatePerSecond: parseUnits("0.004432685185185190", 18), + excessStateTransferFeePer: parseUnits("0.045", 8), }, // WETH { @@ -111,6 +114,7 @@ import { deploy, IDeployConfig } from "./helpers"; decimals: 18, rewardTokenAddress: "0x91c89A94567980f0e9723b487b0beD586eE96aa7", rewardRatePerSecond: parseUnits("0.017881504629629600", 18), + excessStateTransferFeePer: parseUnits("0.045", 8), }, ], }; diff --git a/scripts/helpers.ts b/scripts/helpers.ts index 118c2c9..b9212c4 100644 --- a/scripts/helpers.ts +++ b/scripts/helpers.ts @@ -29,6 +29,7 @@ interface IAddTokenParameters { decimals: number; rewardTokenAddress: string; rewardRatePerSecond: BigNumberish; + excessStateTransferFeePer: BigNumberish; } interface IContracts { @@ -48,29 +49,15 @@ interface IDeployConfig { tokens: IAddTokenParameters[]; } -const wait = (time: number) : Promise => { - return new Promise((resolve)=>{ +const wait = (time: number): Promise => { + return new Promise((resolve) => { setTimeout(resolve, time); }); -} - +}; const deploy = async (deployConfig: IDeployConfig) => { const contracts = await deployCoreContracts(deployConfig.trustedForwarder, deployConfig.pauser); - // const contracts: IContracts = { - // executorManager: await ethers.getContractAt("ExecutorManager","0xbd761D917fB77381B4398Bda89C7F0d9A2BD1399"), - // tokenManager: await ethers.getContractAt("TokenManager","0xd8Ce41FDF0fE96ea4F457d2A22faAF1d128C0954"), - // lpToken: await ethers.getContractAt("LPToken", "0xc49B65e5a350292Afda1f239eBefE562668717c2"), - // liquidityProviders: await ethers.getContractAt("LiquidityProviders", "0xebaB24F13de55789eC1F3fFe99A285754e15F7b9"), - // liquidityPool: await ethers.getContractAt("LiquidityPool", "0x2A5c2568b10A0E826BfA892Cf21BA7218310180b"), - // liquidityFarming: await ethers.getContractAt("HyphenLiquidityFarming", "0x781f4EfC37a08C0ea6631204E46B206330c8c161"), - // whitelistPeriodManager: await ethers.getContractAt("WhitelistPeriodManager","0x684F574CA8C6b52C2b713ad1D1eAcDDF3976e7EB"), - // svgHelperMap: { - // "0xA7D7079b0FEaD91F3e65f86E8915Cb59c1a4C664" : await ethers.getContractAt("SvgHelperBase", "0x0f66A75E8FB3980019d4DD8496c1A24efe4E0D89"), - // "0x49D5c2BdFfac6CE2BFdB6640F4F80f226bc10bAB" : await ethers.getContractAt("SvgHelperBase", "0x5F59206E2965D91a535Ac904F40193aC94365556") - // } - // } for (const token of deployConfig.tokens) { await addTokenSupport(contracts, token); contracts.svgHelperMap[token.tokenAddress] = await deploySvgHelper( @@ -113,9 +100,12 @@ async function deployCoreContracts(trustedForwarder: string, pauser: string): Pr await executorManager.deployed(); console.log("ExecutorManager deployed to:", executorManager.address); await wait(5000); - const TokenManager = await ethers.getContractFactory("TokenManager"); + console.log("Deploying TokenManager..."); - const tokenManager = await TokenManager.deploy(trustedForwarder); + const tokenManager = (await upgrades.deployProxy(await ethers.getContractFactory("TokenManager"), [ + trustedForwarder, + pauser, + ])) as TokenManager; await tokenManager.deployed(); console.log("TokenManager deployed to:", tokenManager.address); @@ -237,7 +227,6 @@ const configure = async (contracts: IContracts, bicoOwner: string) => { await wait(5000); await (await contracts.whitelistPeriodManager.transferOwnership(bicoOwner)).wait(); - console.log(`Transferred Ownership to ${bicoOwner}`); }; @@ -272,6 +261,9 @@ const addTokenSupport = async (contracts: IContracts, token: IAddTokenParameters ) ).wait(); + console.log(`Setting Excess State Transfer Fee % for ${token.tokenAddress}...`); + await (await contracts.tokenManager.changeExcessStateFee(token.tokenAddress, token.excessStateTransferFeePer)).wait(); + console.log(`Setting Whitelist Period Fee Config for ${token.tokenAddress}...`); await ( await contracts.whitelistPeriodManager.setCap( diff --git a/test/LPToken.tests.ts b/test/LPToken.tests.ts index 32b13ef..7aa3b46 100644 --- a/test/LPToken.tests.ts +++ b/test/LPToken.tests.ts @@ -38,8 +38,11 @@ describe("LPTokenTests", function () { beforeEach(async function () { [owner, pauser, charlie, bob, tf, , executor] = await ethers.getSigners(); - const tokenManagerFactory = await ethers.getContractFactory("TokenManager"); - tokenManager = await tokenManagerFactory.deploy(tf.address); + tokenManager = (await upgrades.deployProxy(await ethers.getContractFactory("TokenManager"), [ + tf.address, + pauser.address, + ])) as TokenManager; + await tokenManager.deployed(); const erc20factory = await ethers.getContractFactory("ERC20Token"); token = (await upgrades.deployProxy(erc20factory, ["USDT", "USDT"])) as ERC20Token; diff --git a/test/LiquidityFarming.tests.ts b/test/LiquidityFarming.tests.ts index 5b1bdd8..9fa43d4 100644 --- a/test/LiquidityFarming.tests.ts +++ b/test/LiquidityFarming.tests.ts @@ -52,8 +52,11 @@ describe("LiquidityFarmingTests", function () { beforeEach(async function () { [owner, pauser, charlie, bob, tf, , executor] = await ethers.getSigners(); - const tokenManagerFactory = await ethers.getContractFactory("TokenManager"); - tokenManager = (await tokenManagerFactory.deploy(tf.address)) as TokenManager; + tokenManager = (await upgrades.deployProxy(await ethers.getContractFactory("TokenManager"), [ + tf.address, + pauser.address, + ])) as TokenManager; + await tokenManager.deployed(); const erc20factory = await ethers.getContractFactory("ERC20Token"); token = (await upgrades.deployProxy(erc20factory, ["USDT", "USDT"])) as ERC20Token; @@ -153,6 +156,10 @@ describe("LiquidityFarmingTests", function () { await liquidityProviders.addTokenLiquidity(token.address, 10); await liquidityProviders.addTokenLiquidity(token2.address, 10); + await liquidityProviders.connect(bob).addTokenLiquidity(token.address, 10); + await liquidityProviders.connect(bob).addTokenLiquidity(token2.address, 10); + await liquidityProviders.connect(charlie).addTokenLiquidity(token.address, 10); + await liquidityProviders.connect(charlie).addTokenLiquidity(token2.address, 10); }); it("Should be able to deposit lp tokens", async function () { @@ -193,6 +200,27 @@ describe("LiquidityFarmingTests", function () { expect(await farmingContract.pendingToken(2)).to.equal(1000); expect(await farmingContract.getNftIdsStaked(owner.address)).to.deep.equal([1, 2].map(BigNumber.from)); }); + + it("Should be able to fetch correct nft index", async function () { + await farmingContract.initalizeRewardPool(token2.address, token.address, 10); + await farmingContract.deposit(1, owner.address); + await farmingContract.deposit(2, owner.address); + await farmingContract.connect(bob).deposit(3, bob.address); + await farmingContract.connect(bob).deposit(4, bob.address); + await farmingContract.connect(charlie).deposit(5, charlie.address); + await farmingContract.connect(charlie).deposit(6, charlie.address); + + expect(await farmingContract.getStakedNftIndex(owner.address, 1)).to.equal(0); + expect(await farmingContract.getStakedNftIndex(owner.address, 2)).to.equal(1); + expect(await farmingContract.getStakedNftIndex(bob.address, 3)).to.equal(0); + expect(await farmingContract.getStakedNftIndex(bob.address, 4)).to.equal(1); + expect(await farmingContract.getStakedNftIndex(charlie.address, 5)).to.equal(0); + expect(await farmingContract.getStakedNftIndex(charlie.address, 6)).to.equal(1); + + await expect(farmingContract.getStakedNftIndex(owner.address, 3)).to.be.revertedWith("ERR__NFT_NOT_STAKED"); + await expect(farmingContract.getStakedNftIndex(bob.address, 1)).to.be.revertedWith("ERR__NFT_NOT_STAKED"); + await expect(farmingContract.getStakedNftIndex(charlie.address, 4)).to.be.revertedWith("ERR__NFT_NOT_STAKED"); + }); }); describe("Withdraw", async () => { @@ -247,6 +275,14 @@ describe("LiquidityFarmingTests", function () { await expect(farmingContract.connect(bob).extractRewards(1, bob.address)).to.be.revertedWith("ERR__NOT_OWNER"); }); + it("Should prevent others from withdrawing using v2", async function () { + await liquidityProviders.addTokenLiquidity(token.address, 10); + await liquidityProviders.connect(bob).addTokenLiquidity(token.address, 10); + await farmingContract.deposit(1, owner.address); + await farmingContract.connect(bob).deposit(2, bob.address); + await expect(farmingContract.connect(bob).withdrawV2(1, bob.address, 0)).to.be.revertedWith("ERR__NOT_OWNER"); + }); + it("Should be able to calculate correct rewards correctly", async function () { await liquidityProviders.addTokenLiquidity(token.address, 10); await liquidityProviders.addTokenLiquidity(token2.address, 10); @@ -520,6 +556,85 @@ describe("LiquidityFarmingTests", function () { expect((await farmingContract.nftInfo(4)).isStaked).to.be.false; }); + it("Should be able to withdrawing using withdraw v2", async function () { + await liquidityProviders.addTokenLiquidity(token.address, 10); + await liquidityProviders.addTokenLiquidity(token2.address, 10); + await liquidityProviders.connect(bob).addTokenLiquidity(token.address, 60); + await liquidityProviders.connect(bob).addTokenLiquidity(token2.address, 60); + + await farmingContract.deposit(1, owner.address); + await advanceTime(100); + const time1 = 1; + await farmingContract.deposit(2, owner.address); + await advanceTime(300); + const time2 = 1; + await farmingContract.connect(bob).deposit(3, bob.address); + + await advanceTime(500); + const time3 = 1; + await farmingContract.connect(bob).deposit(4, bob.address); + await advanceTime(900); + + const expectedRewards = [ + Math.floor( + 100 * 10 + + time1 * 10 + + 300 * 10 + + time2 * 10 + + (500 * 10) / 7 + + (time3 * 10) / 7 + + (900 * 10) / 7 + + (3 * 10) / 7 + ), + Math.floor(15 * (300 + time2 + 500 + time3 + 900 / 7) + (4 * 15) / 7), + Math.floor((500 * 6 * 10 + time3 * 6 * 10 + 900 * 6 * 10 + 3 * 6 * 10 + 2 * 7 * 10) / 7), + Math.floor(15 * ((900 * 6) / 7) + (4 * 15 * 6) / 7 + 2 * 15), + ]; + + await token.transfer(farmingContract.address, ethers.BigNumber.from(10).pow(18)); + await token2.transfer(farmingContract.address, ethers.BigNumber.from(10).pow(18)); + + let index = await farmingContract.getStakedNftIndex(owner.address, 1); + await expect(() => farmingContract.withdrawV2(1, owner.address, index)).to.changeTokenBalances( + token2, + [farmingContract, owner], + [-expectedRewards[0], expectedRewards[0]] + ); + index = await farmingContract.getStakedNftIndex(owner.address, 2); + await expect(() => farmingContract.withdrawV2(2, owner.address, index)).to.changeTokenBalances( + token, + [farmingContract, owner], + [-expectedRewards[1], expectedRewards[1]] + ); + index = await farmingContract.getStakedNftIndex(bob.address, 3); + await expect(() => farmingContract.connect(bob).withdrawV2(3, bob.address, index)).to.changeTokenBalances( + token2, + [farmingContract, bob], + [-expectedRewards[2], expectedRewards[2]] + ); + index = await farmingContract.getStakedNftIndex(bob.address, 4); + await expect(() => farmingContract.connect(bob).withdrawV2(4, bob.address, index)).to.changeTokenBalances( + token, + [farmingContract, bob], + [-expectedRewards[3], expectedRewards[3]] + ); + + expect(await lpToken.ownerOf(1)).to.equal(owner.address); + expect(await lpToken.ownerOf(2)).to.equal(owner.address); + expect(await lpToken.ownerOf(3)).to.equal(bob.address); + expect(await lpToken.ownerOf(4)).to.equal(bob.address); + + expect(await farmingContract.pendingToken(1)).to.equal(0); + expect(await farmingContract.pendingToken(2)).to.equal(0); + expect(await farmingContract.pendingToken(3)).to.equal(0); + expect(await farmingContract.pendingToken(4)).to.equal(0); + + expect((await farmingContract.nftInfo(1)).isStaked).to.be.false; + expect((await farmingContract.nftInfo(2)).isStaked).to.be.false; + expect((await farmingContract.nftInfo(3)).isStaked).to.be.false; + expect((await farmingContract.nftInfo(4)).isStaked).to.be.false; + }); + it("Should be able to send correct amount of rewards while withdrawing lp token immediately if available", async function () { await liquidityProviders.addTokenLiquidity(token.address, 10); diff --git a/test/LiquidityPool.tests.ts b/test/LiquidityPool.tests.ts index 00f42c2..48d05cb 100644 --- a/test/LiquidityPool.tests.ts +++ b/test/LiquidityPool.tests.ts @@ -11,7 +11,8 @@ import { // eslint-disable-next-line node/no-missing-import } from "../typechain"; import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; -import { BigNumber } from "ethers"; +import { BigNumber, BigNumberish } from "ethers"; +import { parseEther } from "ethers/lib/utils"; let { getLocaleString } = require("./utils"); @@ -28,6 +29,7 @@ describe("LiquidityPoolTests", function () { let trustedForwarder = "0xFD4973FeB2031D4409fB57afEE5dF2051b171104"; let equilibriumFee = 10000000; + let excessStateFee = 4500000; let maxFee = 200000000; let tokenAddress: string; let tag: string = "HyphenUI"; @@ -42,6 +44,7 @@ describe("LiquidityPoolTests", function () { const communityPerWalletMaxCap = getLocaleString(1000 * 1e18); const commuintyPerTokenMaxCap = getLocaleString(500000 * 1e18); const tokenMaxCap = getLocaleString(1000000 * 1e18); + const baseDivisor = ethers.BigNumber.from(10).pow(10); const communityPerWalletNativeMaxCap = getLocaleString(1 * 1e18); const commuintyPerTokenNativeMaxCap = getLocaleString(100 * 1e18); @@ -55,8 +58,11 @@ describe("LiquidityPoolTests", function () { beforeEach(async function () { [owner, pauser, charlie, bob, tf, proxyAdmin, executor] = await ethers.getSigners(); - const tokenManagerFactory = await ethers.getContractFactory("TokenManager"); - tokenManager = await tokenManagerFactory.deploy(trustedForwarder); + tokenManager = (await upgrades.deployProxy(await ethers.getContractFactory("TokenManager"), [ + tf.address, + pauser.address, + ])) as TokenManager; + await tokenManager.deployed(); const executorManagerFactory = await ethers.getContractFactory("ExecutorManager"); executorManager = await executorManagerFactory.deploy(); @@ -105,6 +111,7 @@ describe("LiquidityPoolTests", function () { max: maxTokenCap, }; await tokenManager.connect(owner).setDepositConfig([1], [tokenAddress], [tokenDepositConfig]); + await tokenManager.connect(owner).changeExcessStateFee(tokenAddress, excessStateFee); // Add supported Native token await tokenManager @@ -138,10 +145,13 @@ describe("LiquidityPoolTests", function () { for (const signer of [owner, bob, charlie]) { await token.mint(signer.address, ethers.BigNumber.from(100000000).mul(ethers.BigNumber.from(10).pow(18))); + await token + .connect(signer) + .approve(liquidityPool.address, ethers.BigNumber.from(100000000).mul(ethers.BigNumber.from(10).pow(18))); } }); - async function addTokenLiquidity(tokenAddress: string, tokenValue: string, sender: SignerWithAddress) { + async function addTokenLiquidity(tokenAddress: string, tokenValue: BigNumberish, sender: SignerWithAddress) { let tx = await token.connect(sender).approve(liquidityProviders.address, tokenValue); await tx.wait(); await liquidityProviders.connect(sender).addTokenLiquidity(tokenAddress, tokenValue); @@ -180,10 +190,23 @@ describe("LiquidityPoolTests", function () { }); } - async function sendFundsToUser(tokenAddress: string, amount: string, receiver: string, tokenGasPrice: string, depositHash?: string) { + async function sendFundsToUser( + tokenAddress: string, + amount: string, + receiver: string, + tokenGasPrice: string, + depositHash?: string + ) { return await liquidityPool .connect(executor) - .sendFundsToUser(tokenAddress, amount, receiver, depositHash? depositHash: dummyDepositHash, tokenGasPrice, 137); + .sendFundsToUser( + tokenAddress, + amount, + receiver, + depositHash ? depositHash : dummyDepositHash, + tokenGasPrice, + 137 + ); } async function checkStorage() { @@ -442,7 +465,13 @@ describe("LiquidityPoolTests", function () { await tx.wait(); let rewardPoolBeforeTransfer = await liquidityPool.incentivePool(NATIVE); - tx = await sendFundsToUser(NATIVE, amountToWithdraw, receiver, "0", "0xbf4d8d58e7905ad39ea2e4b8c8ae0cae89e5877d8540b03a299a0b14544c1e6a"); + tx = await sendFundsToUser( + NATIVE, + amountToWithdraw, + receiver, + "0", + "0xbf4d8d58e7905ad39ea2e4b8c8ae0cae89e5877d8540b03a299a0b14544c1e6a" + ); await tx.wait(); const amountToDeposit = BigNumber.from(minTokenCap).toString(); @@ -488,8 +517,7 @@ describe("LiquidityPoolTests", function () { const liquidityToBeAdded = getLocaleString(50 * 1e18); await addNativeLiquidity(liquidityToBeAdded, owner); - const amountToWithdraw = BigNumber.from(minTokenCap) - .add(BigNumber.from(getLocaleString(5 * 1e18))); + const amountToWithdraw = BigNumber.from(minTokenCap).add(BigNumber.from(getLocaleString(5 * 1e18))); let receiver = await getReceiverAddress(); let toChainId = 1; await executorManager.addExecutor(executor.address); @@ -500,16 +528,21 @@ describe("LiquidityPoolTests", function () { let rewardPoolBeforeTransfer = await liquidityPool.incentivePool(NATIVE); let transferFeePercentage = await liquidityPool.getTransferFee(NATIVE, amountToWithdraw); - + let excessFeePercentage = transferFeePercentage.sub(BigNumber.from("10000000")); let expectedRewardAmount = amountToWithdraw.mul(excessFeePercentage).div(1e10); - tx = await sendFundsToUser(NATIVE, amountToWithdraw.toString(), receiver, "0", "0xbf4d8d58e7905ad39ea2e4b8c8ae0cae89e5877d8540b03a299a0b14544c1e6a"); + tx = await sendFundsToUser( + NATIVE, + amountToWithdraw.toString(), + receiver, + "0", + "0xbf4d8d58e7905ad39ea2e4b8c8ae0cae89e5877d8540b03a299a0b14544c1e6a" + ); let rewardPoolAfterTrasnfer = await liquidityPool.incentivePool(NATIVE); let rewardPoolDiff = rewardPoolAfterTrasnfer.sub(rewardPoolBeforeTransfer); expect(rewardPoolDiff.eq(expectedRewardAmount)).to.be.true; - }); }); @@ -753,4 +786,81 @@ describe("LiquidityPoolTests", function () { }) ).to.be.reverted; }); + + describe("Transfer Fee", async function () { + it("Should calculate fee properly", async function () { + const providedLiquidity = ethers.BigNumber.from(minTokenCap).mul(10); + await addTokenLiquidity(tokenAddress, providedLiquidity, owner); + + const amount = ethers.BigNumber.from(minTokenCap); + await executorManager.connect(owner).addExecutor(await executor.getAddress()); + const resultingLiquidity = providedLiquidity.sub(amount); + + const expectedFeePerNum = providedLiquidity.mul(providedLiquidity).mul(equilibriumFee).mul(maxFee); + const expectedFeePerDen = providedLiquidity + .mul(providedLiquidity) + .mul(equilibriumFee) + .add(resultingLiquidity.mul(resultingLiquidity).mul(maxFee - equilibriumFee)); + const expectedFeePer = expectedFeePerNum.div(expectedFeePerDen); + + const expectedTransferFee = amount.mul(expectedFeePer).div(baseDivisor); + const lpFee = amount.mul(equilibriumFee).div(baseDivisor); + const incentivePoolFee = expectedTransferFee.sub(lpFee); + + await expect( + liquidityPool + .connect(executor) + .sendFundsToUser(token.address, amount, await getReceiverAddress(), dummyDepositHash, 0, 137) + ) + .to.emit(liquidityPool, "AssetSent") + .withArgs( + token.address, + amount, + amount.sub(expectedTransferFee), + await getReceiverAddress(), + dummyDepositHash, + 137, + lpFee, + expectedTransferFee, + 0 + ); + expect(await liquidityPool.incentivePool(token.address)).to.equal(incentivePoolFee); + }); + + it("Should collect constant transaction fee in excess state", async function () { + const providedLiquidity = ethers.BigNumber.from(minTokenCap).mul(10); + await addTokenLiquidity(tokenAddress, providedLiquidity, owner); + + await liquidityPool.depositErc20(1, token.address, owner.address, providedLiquidity.mul(10), "test"); + + const amount = ethers.BigNumber.from(minTokenCap); + await executorManager.connect(owner).addExecutor(await executor.getAddress()); + + const expectedTransferFee = amount.mul(excessStateFee).div(baseDivisor); + const lpFee = expectedTransferFee; + const incentivePoolFee = 0; + + for (let i = 0; i < 10; i++) { + const hash = ethers.BigNumber.from(dummyDepositHash).add(i).toHexString(); + await expect( + liquidityPool + .connect(executor) + .sendFundsToUser(token.address, amount, await getReceiverAddress(), hash, 0, 137) + ) + .to.emit(liquidityPool, "AssetSent") + .withArgs( + token.address, + amount, + amount.sub(expectedTransferFee), + await getReceiverAddress(), + hash, + 137, + lpFee, + expectedTransferFee, + 0 + ); + expect(await liquidityPool.incentivePool(token.address)).to.equal(incentivePoolFee); + } + }); + }); }); diff --git a/test/LiquidityProviders.test.ts b/test/LiquidityProviders.test.ts index 8358b1b..4044722 100644 --- a/test/LiquidityProviders.test.ts +++ b/test/LiquidityProviders.test.ts @@ -107,8 +107,11 @@ describe("LiquidityProviderTests", function () { beforeEach(async function () { [owner, pauser, charlie, bob, tf, , executor] = await ethers.getSigners(); - const tokenManagerFactory = await ethers.getContractFactory("TokenManager"); - tokenManager = await tokenManagerFactory.deploy(tf.address); + tokenManager = (await upgrades.deployProxy(await ethers.getContractFactory("TokenManager"), [ + tf.address, + pauser.address, + ])) as TokenManager; + await tokenManager.deployed(); const erc20factory = await ethers.getContractFactory("ERC20Token"); token = (await upgrades.deployProxy(erc20factory, ["USDT", "USDT"])) as ERC20Token; @@ -682,6 +685,14 @@ describe("LiquidityProviderTests", function () { it("Should revert if more shares are burnt than available for reward", async function () { await expect(liquidityProviders.claimFee(1)).to.be.revertedWith("ERR__NO_REWARDS_TO_CLAIM"); }); + + it("Should not mint 0 shares after all shares have been burnt", async function () { + await liquidityProviders.addLpFeeTesting(token.address, 1); + await liquidityProviders.removeLiquidity(1, 99); + expect(await liquidityProviders.totalSharesMinted(token.address)).to.equal(0); + await liquidityProviders.addTokenLiquidity(token.address, 10); + expect((await lpToken.tokenMetadata(3)).shares).to.not.equal(0); + }); }); describe("Real world flow tests", async function () { diff --git a/test/Pauser.tests.ts b/test/Pauser.tests.ts new file mode 100644 index 0000000..b1e1021 --- /dev/null +++ b/test/Pauser.tests.ts @@ -0,0 +1,142 @@ +import { expect } from "chai"; +import { ethers, upgrades } from "hardhat"; +import { + ERC20Token, + LiquidityPool, + LiquidityProvidersTest, + WhitelistPeriodManager, + LPToken, + ExecutorManager, + TokenManager, + // eslint-disable-next-line node/no-missing-import +} from "../typechain"; +import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; +import { BigNumber, ContractTransaction } from "ethers"; + +let { getLocaleString } = require("./utils"); + +describe("PauserTests", function () { + let owner: SignerWithAddress, pauser: SignerWithAddress, bob: SignerWithAddress; + let charlie: SignerWithAddress, tf: SignerWithAddress, executor: SignerWithAddress; + let token: ERC20Token, token2: ERC20Token; + let lpToken: LPToken; + let wlpm: WhitelistPeriodManager; + let liquidityProviders: LiquidityProvidersTest; + let liquidityPool: LiquidityPool; + let executorManager: ExecutorManager; + let tokenManager: TokenManager; + let trustedForwarder = "0xFD4973FeB2031D4409fB57afEE5dF2051b171104"; + const NATIVE = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"; + let BASE: BigNumber = BigNumber.from(10).pow(18); + + const perWalletMaxCap = getLocaleString(1000 * 1e18); + const tokenMaxCap = getLocaleString(1000000 * 1e18); + + const perWalletNativeMaxCap = getLocaleString(1 * 1e18); + const tokenNativeMaxCap = getLocaleString(200 * 1e18); + + beforeEach(async function () { + [owner, pauser, charlie, bob, tf, , executor] = await ethers.getSigners(); + + tokenManager = (await upgrades.deployProxy(await ethers.getContractFactory("TokenManager"), [ + tf.address, + pauser.address, + ])) as TokenManager; + await tokenManager.deployed(); + + const erc20factory = await ethers.getContractFactory("ERC20Token"); + token = (await upgrades.deployProxy(erc20factory, ["USDT", "USDT"])) as ERC20Token; + token2 = (await upgrades.deployProxy(erc20factory, ["USDC", "USDC"])) as ERC20Token; + for (const signer of [owner, bob, charlie]) { + await token.mint(signer.address, ethers.BigNumber.from(100000000).mul(ethers.BigNumber.from(10).pow(18))); + await token2.mint(signer.address, ethers.BigNumber.from(100000000).mul(ethers.BigNumber.from(10).pow(18))); + } + await tokenManager.addSupportedToken(token.address, BigNumber.from(1), BigNumber.from(10).pow(30), 0, 0, 0); + await tokenManager.addSupportedToken(token2.address, BigNumber.from(1), BigNumber.from(10).pow(30), 0, 0, 0); + await tokenManager.addSupportedToken(NATIVE, BigNumber.from(1), BigNumber.from(10).pow(30), 0, 0, 0); + + const executorManagerFactory = await ethers.getContractFactory("ExecutorManager"); + executorManager = await executorManagerFactory.deploy(); + + const lpTokenFactory = await ethers.getContractFactory("LPToken"); + lpToken = (await upgrades.deployProxy(lpTokenFactory, [ + "LPToken", + "LPToken", + tf.address, + pauser.address, + ])) as LPToken; + + const liquidtyProvidersFactory = await ethers.getContractFactory("LiquidityProvidersTest"); + liquidityProviders = (await upgrades.deployProxy(liquidtyProvidersFactory, [ + trustedForwarder, + lpToken.address, + tokenManager.address, + pauser.address, + ])) as LiquidityProvidersTest; + await liquidityProviders.deployed(); + await lpToken.setLiquidityProviders(liquidityProviders.address); + await liquidityProviders.setLpToken(lpToken.address); + + const wlpmFactory = await ethers.getContractFactory("WhitelistPeriodManager"); + wlpm = (await upgrades.deployProxy(wlpmFactory, [ + tf.address, + liquidityProviders.address, + tokenManager.address, + lpToken.address, + pauser.address, + ])) as WhitelistPeriodManager; + await wlpm.setLiquidityProviders(liquidityProviders.address); + await liquidityProviders.setWhiteListPeriodManager(wlpm.address); + await lpToken.setWhiteListPeriodManager(wlpm.address); + await wlpm.setCaps( + [token.address, NATIVE], + [tokenMaxCap, tokenNativeMaxCap], + [perWalletMaxCap, perWalletNativeMaxCap] + ); + await wlpm.setAreWhiteListRestrictionsEnabled(false); + + const lpFactory = await ethers.getContractFactory("LiquidityPool"); + liquidityPool = (await upgrades.deployProxy(lpFactory, [ + executorManager.address, + pauser.address, + tf.address, + tokenManager.address, + liquidityProviders.address, + ])) as LiquidityPool; + await liquidityProviders.setLiquidityPool(liquidityPool.address); + }); + + this.afterEach(async function () { + expect(await token.balanceOf(liquidityProviders.address)).to.equal(0); + expect(await token2.balanceOf(liquidityProviders.address)).to.equal(0); + expect(await ethers.provider.getBalance(liquidityProviders.address)).to.equal(0); + }); + + it("It should allow pauser to renounce it's role", async function () { + await expect(lpToken.connect(pauser).renouncePauser()).to.not.be.reverted; + }); + + it("Should not allow pauser to renounce if contract is paused", async function () { + await lpToken.connect(pauser).pause(); + await expect(lpToken.connect(pauser).renouncePauser()).to.be.revertedWith("Pausable: paused"); + }); + + it("Should not allow pauser to changePauser if contract is paused", async function () { + await lpToken.connect(pauser).pause(); + await expect(lpToken.connect(pauser).changePauser(bob.address)).to.be.revertedWith("Pausable: paused"); + }); + + it("Should allow pauser to change pauser after unpausing", async function () { + await lpToken.connect(pauser).pause(); + await expect(lpToken.connect(pauser).changePauser(bob.address)).to.be.revertedWith("Pausable: paused"); + await lpToken.connect(pauser).unpause(); + await expect(lpToken.connect(pauser).changePauser(bob.address)).to.not.be.reverted; + }); + + it("Should allow pauser to renounce pauser after unpausing", async function () { + await lpToken.connect(pauser).pause(); + await expect(lpToken.connect(pauser).changePauser(bob.address)).to.be.revertedWith("Pausable: paused"); + await lpToken.connect(pauser).unpause(); + await expect(lpToken.connect(pauser).renouncePauser()).to.not.be.reverted; + }); +}); diff --git a/test/Upgradibility.tests.ts b/test/Upgradibility.tests.ts new file mode 100644 index 0000000..f42b6a5 --- /dev/null +++ b/test/Upgradibility.tests.ts @@ -0,0 +1,273 @@ +import { expect, use } from "chai"; +import { ethers, upgrades } from "hardhat"; +import { + ERC20Token, + LiquidityPool, + LiquidityProvidersTest, + ExecutorManager, + LPToken, + WhitelistPeriodManager, + TokenManager, + HyphenLiquidityFarming, + // eslint-disable-next-line node/no-missing-import +} from "../typechain"; +import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; +import { parseEther } from "ethers/lib/utils"; + +let { getLocaleString } = require("./utils"); + +describe("Upgradibility", function () { + let owner: SignerWithAddress, pauser: SignerWithAddress, bob: SignerWithAddress; + let charlie: SignerWithAddress, tf: SignerWithAddress, executor: SignerWithAddress; + let executorManager: ExecutorManager; + let token: ERC20Token, liquidityPool: LiquidityPool; + let lpToken: LPToken; + let wlpm: WhitelistPeriodManager; + let liquidityProviders: LiquidityProvidersTest; + let tokenManager: TokenManager; + let farmingContract: HyphenLiquidityFarming; + + let trustedForwarder = "0xFD4973FeB2031D4409fB57afEE5dF2051b171104"; + let equilibriumFee = 10000000; + let excessStateFee = 4500000; + let maxFee = 200000000; + let tokenAddress: string; + let tag: string = "HyphenUI"; + + const NATIVE = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"; + const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000"; + const minTokenCap = getLocaleString(0.1 * 1e18); + const maxTokenCap = getLocaleString(200000 * 1e18); + const minNativeTokenCap = getLocaleString(1e17); + const maxNativeTokenCap = getLocaleString(25 * 1e18); + + const commuintyPerTokenMaxCap = getLocaleString(500000 * 1e18); + const tokenMaxCap = getLocaleString(1000000 * 1e18); + const baseDivisor = ethers.BigNumber.from(10).pow(10); + + const communityPerWalletNativeMaxCap = getLocaleString(1 * 1e18); + const commuintyPerTokenNativeMaxCap = getLocaleString(100 * 1e18); + const tokenNativeMaxCap = getLocaleString(200 * 1e18); + + const DEPOSIT_EVENT = "Deposit"; + const ASSET_SENT = "AssetSent"; + const DEPOSIT_TOPIC_ID = "0x5fe47ed6d4225326d3303476197d782ded5a4e9c14f479dc9ec4992af4e85d59"; + + let depositHash1: string, depositHash2: string; + + before(async function () { + [owner, pauser, charlie, bob, tf, executor] = await ethers.getSigners(); + + const tokenManagerFactory = await ethers.getContractFactory("TokenManagerOld"); + tokenManager = (await tokenManagerFactory.deploy(trustedForwarder)) as TokenManager; + + const executorManagerFactory = await ethers.getContractFactory("ExecutorManager"); + executorManager = await executorManagerFactory.deploy(); + await executorManager.deployed(); + + const lpTokenFactory = await ethers.getContractFactory("LPToken"); + lpToken = (await upgrades.deployProxy(lpTokenFactory, [ + "Hyphen LP Token", + "HPT", + trustedForwarder, + pauser.address, + ])) as LPToken; + + const liquidtyProvidersFactory = await ethers.getContractFactory("LiquidityProvidersOld"); + liquidityProviders = (await upgrades.deployProxy(liquidtyProvidersFactory, [ + trustedForwarder, + lpToken.address, + tokenManager.address, + pauser.address, + ])) as LiquidityProvidersTest; + + const liquidtyPoolFactory = await ethers.getContractFactory("LiquidityPoolOld"); + liquidityPool = (await upgrades.deployProxy(liquidtyPoolFactory, [ + executorManager.address, + await pauser.getAddress(), + trustedForwarder, + tokenManager.address, + liquidityProviders.address, + ])) as LiquidityPool; + + await liquidityPool.deployed(); + + await liquidityProviders.setLiquidityPool(liquidityPool.address); + + const erc20factory = await ethers.getContractFactory("ERC20Token"); + token = (await upgrades.deployProxy(erc20factory, ["USDT", "USDT"])) as ERC20Token; + tokenAddress = token.address; + + // Add supported ERC20 token + await tokenManager + .connect(owner) + .addSupportedToken(tokenAddress, minTokenCap, maxTokenCap, equilibriumFee, maxFee, 0); + + let tokenDepositConfig = { + min: minTokenCap, + max: maxTokenCap, + }; + await tokenManager.connect(owner).setDepositConfig([1], [tokenAddress], [tokenDepositConfig]); + + // Add supported Native token + await tokenManager + .connect(owner) + .addSupportedToken(NATIVE, minNativeTokenCap, maxNativeTokenCap, equilibriumFee, maxFee, 0); + + let nativeDepositConfig = { + min: minTokenCap, + max: maxTokenCap, + }; + await tokenManager.connect(owner).setDepositConfig([1], [NATIVE], [nativeDepositConfig]); + + await lpToken.setLiquidityProviders(liquidityProviders.address); + + await executorManager.addExecutor(executor.address); + + const wlpmFactory = await ethers.getContractFactory("WhitelistPeriodManager"); + wlpm = (await upgrades.deployProxy(wlpmFactory, [ + trustedForwarder, + liquidityProviders.address, + tokenManager.address, + lpToken.address, + pauser.address, + ])) as WhitelistPeriodManager; + + await wlpm.setCaps( + [token.address, NATIVE], + [tokenMaxCap, tokenNativeMaxCap], + [commuintyPerTokenMaxCap, commuintyPerTokenNativeMaxCap] + ); + + await liquidityProviders.setWhiteListPeriodManager(wlpm.address); + await lpToken.setWhiteListPeriodManager(wlpm.address); + + const farmingFactory = await ethers.getContractFactory("HyphenLiquidityFarmingOld"); + farmingContract = (await upgrades.deployProxy(farmingFactory, [ + tf.address, + pauser.address, + liquidityProviders.address, + lpToken.address, + ])) as HyphenLiquidityFarming; + await wlpm.setIsExcludedAddressStatus([farmingContract.address], [true]); + + for (const signer of [owner, bob, charlie]) { + await token.mint(signer.address, ethers.BigNumber.from(100000000).mul(ethers.BigNumber.from(10).pow(18))); + await token + .connect(signer) + .approve(liquidityPool.address, ethers.BigNumber.from(100000000).mul(ethers.BigNumber.from(10).pow(18))); + await token + .connect(signer) + .approve(liquidityProviders.address, ethers.BigNumber.from(100000000).mul(ethers.BigNumber.from(10).pow(18))); + } + await farmingContract.initalizeRewardPool(NATIVE, token.address, 10); + await farmingContract.initalizeRewardPool(token.address, NATIVE, 10); + + await populateContractsState(); + }); + + async function populateContractsState() { + // Add Liquidity + await liquidityProviders.connect(owner).addTokenLiquidity(token.address, parseEther("10")); + await liquidityProviders.connect(owner).addNativeLiquidity({ value: parseEther("10") }); + + // Stake + await lpToken.approve(farmingContract.address, 1); + await lpToken.approve(farmingContract.address, 2); + await farmingContract.deposit(1, owner.address); + await farmingContract.deposit(2, owner.address); + + // Deposit Transaction + const dr = await liquidityPool.connect(bob).depositErc20(1, token.address, bob.address, parseEther("1"), "test"); + depositHash1 = dr.hash; + const dr2 = await liquidityPool.connect(bob).depositNative(bob.address, 1, "test", { value: parseEther("1") }); + depositHash2 = dr2.hash; + } + + it("Should be able to upgrade contracts", async function () { + const oldTokenManager = tokenManager.address; + + (await upgrades.upgradeProxy(liquidityPool.address, await ethers.getContractFactory("LiquidityPool"))).deployed(); + ( + await upgrades.upgradeProxy(liquidityProviders.address, await ethers.getContractFactory("LiquidityProviders")) + ).deployed(); + ( + await upgrades.upgradeProxy(farmingContract.address, await ethers.getContractFactory("HyphenLiquidityFarming")) + ).deployed(); + tokenManager = (await upgrades.deployProxy(await ethers.getContractFactory("TokenManager"), [ + tf.address, + pauser.address, + ])) as TokenManager; + await tokenManager.deployed(); + + expect(tokenManager.address).to.not.equal(oldTokenManager); + + await liquidityPool.setTokenManager(tokenManager.address); + await liquidityProviders.setTokenManager(tokenManager.address); + await wlpm.setTokenManager(tokenManager.address); + + expect(await liquidityPool.tokenManager()).to.equal(tokenManager.address); + + // Add supported ERC20 token + await tokenManager + .connect(owner) + .addSupportedToken(tokenAddress, minTokenCap, maxTokenCap, equilibriumFee, maxFee, 0); + + let tokenDepositConfig = { + min: minTokenCap, + max: maxTokenCap, + }; + await tokenManager.connect(owner).setDepositConfig([1], [tokenAddress], [tokenDepositConfig]); + + // Add supported Native token + await tokenManager + .connect(owner) + .addSupportedToken(NATIVE, minNativeTokenCap, maxNativeTokenCap, equilibriumFee, maxFee, 0); + + let nativeDepositConfig = { + min: minTokenCap, + max: maxTokenCap, + }; + await tokenManager.connect(owner).setDepositConfig([1], [NATIVE], [nativeDepositConfig]); + + await tokenManager.changeExcessStateFee(NATIVE, excessStateFee); + await tokenManager.changeExcessStateFee(token.address, excessStateFee); + }); + + it("Should be able to unstake tokens", async function () { + await expect(farmingContract.withdraw(1, owner.address)).to.not.be.reverted; + await expect(farmingContract.withdraw(2, owner.address)).to.not.be.reverted; + expect(await lpToken.ownerOf(1)).to.equal(owner.address); + expect(await lpToken.ownerOf(2)).to.equal(owner.address); + }); + + it("Should be able to process exit transaction", async function () { + const tokenBalance = await token.balanceOf(owner.address); + const nativeBalance = await ethers.provider.getBalance(owner.address); + + await expect( + liquidityPool.connect(executor).sendFundsToUser(token.address, parseEther("1"), owner.address, depositHash1, 0, 1) + ).to.not.be.reverted; + + await expect( + liquidityPool.connect(executor).sendFundsToUser(NATIVE, parseEther("1"), owner.address, depositHash2, 0, 1) + ).to.not.be.reverted; + + expect((await token.balanceOf(owner.address)).gte(tokenBalance)).to.be.true; + expect((await ethers.provider.getBalance(owner.address)).gte(nativeBalance)).to.be.true; + }); + + it("Should be able to withdraw liquidity", async function () { + const rewards1 = await liquidityProviders.getFeeAccumulatedOnNft(1); + const rewards2 = await liquidityProviders.getFeeAccumulatedOnNft(2); + await expect(() => liquidityProviders.removeLiquidity(1, parseEther("0.05"))).to.changeTokenBalances( + token, + [liquidityPool, owner], + [parseEther("-0.05").sub(rewards1), parseEther("0.05").add(rewards1)] + ); + await expect(() => liquidityProviders.removeLiquidity(2, parseEther("0.05"))).to.changeEtherBalances( + [liquidityPool, owner], + [parseEther("-0.05").sub(rewards2), parseEther("0.05").add(rewards2)] + ); + }); +}); diff --git a/test/WhitelistPeriodManager.test.ts b/test/WhitelistPeriodManager.test.ts index 98996d7..8ea623c 100644 --- a/test/WhitelistPeriodManager.test.ts +++ b/test/WhitelistPeriodManager.test.ts @@ -29,8 +29,11 @@ describe("WhiteListPeriodManager", function () { beforeEach(async function () { [owner, pauser, charlie, bob, dan, elon, tf, , executor] = await ethers.getSigners(); - const tokenManagerFactory = await ethers.getContractFactory("TokenManager"); - tokenManager = await tokenManagerFactory.deploy(tf.address); + tokenManager = (await upgrades.deployProxy(await ethers.getContractFactory("TokenManager"), [ + tf.address, + pauser.address, + ])) as TokenManager; + await tokenManager.deployed(); const erc20factory = await ethers.getContractFactory("ERC20Token"); token = (await upgrades.deployProxy(erc20factory, ["USDT", "USDT"])) as ERC20Token;