diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index 9e7f080..63650cb 100644 --- a/.github/actions/setup/action.yml +++ b/.github/actions/setup/action.yml @@ -8,7 +8,7 @@ runs: - name: Setup node uses: actions/setup-node@v3 with: - node-version: "16.18.x" + node-version: "18.16.x" cache: npm - name: Install packages run: npm install diff --git a/contracts/Factory.sol b/contracts/Factory.sol new file mode 100644 index 0000000..52a6702 --- /dev/null +++ b/contracts/Factory.sol @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol"; + +import {IFactory} from "./interfaces/IFactory.sol"; + +abstract contract Factory is IFactory, OwnableUpgradeable, PausableUpgradeable, UUPSUpgradeable { + mapping(uint8 => address) internal _implementations; + mapping(bytes32 => bool) private _usedSalts; + + /** + * @dev It is used exclusively for storing information about the detached proxies. + * + * `_msgSender()` -> `poolName` -> `poolType` -> `proxy` + */ + mapping(address => mapping(string => mapping(uint8 => address))) public deployedProxies; + + function __Factory_init() internal onlyInitializing {} + + /** + * @notice Returns contract to normal state. + */ + function pause() external onlyOwner { + _pause(); + } + + /** + * @notice Triggers stopped state. + */ + function unpause() external onlyOwner { + _unpause(); + } + + /** + * @notice The function to set implementation for the specific pool. + * + * @param poolType_ the type of the pool. + * @param implementation_ the implementation the pool will point to. + */ + function setImplementation(uint8 poolType_, address implementation_) public onlyOwner { + _implementations[poolType_] = implementation_; + } + + /** + * @notice The function to get implementation of the specific pools. + * + * @param poolType_ the type of the pools. + * @return implementation the implementation which the pool points to. + */ + function getImplementation(uint8 poolType_) public view returns (address) { + return _implementations[poolType_]; + } + + /** + * @notice The function to deploy new `ERC1967Proxy`. + * + * @param poolType_ the type of the pool. + * @param poolName_ the name of the pool. + * @return proxy the proxy address for the `poolType_`. + */ + function _deploy2(uint8 poolType_, string calldata poolName_) internal returns (address) { + require(bytes(poolName_).length != 0, "F: poolName_ is empty"); + bytes32 salt_ = _calculatePoolSalt(_msgSender(), poolName_, poolType_); + + address implementation_ = getImplementation(poolType_); + require(implementation_ != address(0), "F: implementation not found"); + + require(!_usedSalts[salt_], "F: salt used"); + _usedSalts[salt_] = true; + + address proxy_ = address(new ERC1967Proxy{salt: salt_}(implementation_, bytes(""))); + + deployedProxies[_msgSender()][poolName_][poolType_] = proxy_; + + emit ProxyDeployed(proxy_, implementation_, poolType_, poolName_); + + return proxy_; + } + + function _predictPoolAddress( + uint8 poolType_, + string calldata poolName_, + address sender_ + ) internal view returns (address) { + bytes32 salt_ = _calculatePoolSalt(sender_, poolName_, uint8(poolType_)); + + bytes32 bytecodeHash_ = keccak256( + abi.encodePacked(type(ERC1967Proxy).creationCode, abi.encode(getImplementation(poolType_), bytes(""))) + ); + + return Create2.computeAddress(salt_, bytecodeHash_); + } + + function _calculatePoolSalt( + address sender_, + string calldata poolName_, + uint8 poolType_ + ) internal pure returns (bytes32) { + return keccak256(abi.encodePacked(sender_, poolName_, poolType_)); + } + + function _authorizeUpgrade(address) internal view override onlyOwner {} +} diff --git a/contracts/L1/Distribution.sol b/contracts/L1/Distribution.sol index db20854..b37fe86 100644 --- a/contracts/L1/Distribution.sol +++ b/contracts/L1/Distribution.sol @@ -9,9 +9,9 @@ import {PRECISION} from "@solarity/solidity-lib/utils/Globals.sol"; import {LinearDistributionIntervalDecrease} from "../libs/LinearDistributionIntervalDecrease.sol"; -import {IDistribution} from "../interfaces/IDistribution.sol"; - -import {L1Sender} from "./L1Sender.sol"; +import {IDistribution} from "../interfaces/L1/IDistribution.sol"; +import {IFeeConfig} from "../interfaces/L1/IFeeConfig.sol"; +import {IL1Sender} from "../interfaces/L1/IL1Sender.sol"; contract Distribution is IDistribution, OwnableUpgradeable, UUPSUpgradeable { using SafeERC20 for IERC20; @@ -20,6 +20,7 @@ contract Distribution is IDistribution, OwnableUpgradeable, UUPSUpgradeable { address public depositToken; address public l1Sender; + address public feeConfig; // Pool storage Pool[] public pools; @@ -55,6 +56,7 @@ contract Distribution is IDistribution, OwnableUpgradeable, UUPSUpgradeable { function Distribution_init( address depositToken_, address l1Sender_, + address feeConfig_, Pool[] calldata poolsInfo_ ) external initializer { __Ownable_init(); @@ -66,6 +68,7 @@ contract Distribution is IDistribution, OwnableUpgradeable, UUPSUpgradeable { depositToken = depositToken_; l1Sender = l1Sender_; + feeConfig = feeConfig_; } /**********************************************************************************************/ @@ -82,7 +85,14 @@ contract Distribution is IDistribution, OwnableUpgradeable, UUPSUpgradeable { function editPool(uint256 poolId_, Pool calldata pool_) external onlyOwner poolExists(poolId_) { _validatePool(pool_); - require(pools[poolId_].isPublic == pool_.isPublic, "DS: invalid pool type"); + + Pool storage pool = pools[poolId_]; + require(pool.isPublic == pool_.isPublic, "DS: invalid pool type"); + if (pool_.payoutStart > block.timestamp) { + require(pool.payoutStart == pool_.payoutStart, "DS: invalid payout start value"); + require(pool.withdrawLockPeriod == pool_.withdrawLockPeriod, "DS: invalid WLP value"); + require(pool.withdrawLockPeriodAfterStake == pool_.withdrawLockPeriodAfterStake, "DS: invalid WLPAS value"); + } PoolData storage poolData = poolsData[poolId_]; uint256 currentPoolRate_ = _getCurrentPoolRate(poolId_); @@ -174,7 +184,7 @@ contract Distribution is IDistribution, OwnableUpgradeable, UUPSUpgradeable { userData.pendingRewards = 0; // Transfer rewards - L1Sender(l1Sender).sendMintMessage{value: msg.value}(receiver_, pendingRewards_, user_); + IL1Sender(l1Sender).sendMintMessage{value: msg.value}(receiver_, pendingRewards_, user_); emit UserClaimed(poolId_, user_, receiver_, pendingRewards_); } @@ -327,9 +337,18 @@ contract Distribution is IDistribution, OwnableUpgradeable, UUPSUpgradeable { uint256 overplus_ = overplus(); require(overplus_ > 0, "DS: overplus is zero"); + (uint256 feePercent_, address treasuryAddress_) = IFeeConfig(feeConfig).getFeeAndTreasury(address(this)); + + uint256 fee_ = (overplus_ * feePercent_) / PRECISION; + if (fee_ != 0) { + IERC20(depositToken).safeTransfer(treasuryAddress_, fee_); + + overplus_ -= fee_; + } + IERC20(depositToken).safeTransfer(l1Sender, overplus_); - bytes memory bridgeMessageId_ = L1Sender(l1Sender).sendDepositToken{value: msg.value}( + bytes memory bridgeMessageId_ = IL1Sender(l1Sender).sendDepositToken{value: msg.value}( gasLimit_, maxFeePerGas_, maxSubmissionCost_ @@ -343,7 +362,6 @@ contract Distribution is IDistribution, OwnableUpgradeable, UUPSUpgradeable { /**********************************************************************************************/ /*** UUPS ***/ /**********************************************************************************************/ - function removeUpgradeability() external onlyOwner { isNotUpgradeable = true; } diff --git a/contracts/L1/FeeConfig.sol b/contracts/L1/FeeConfig.sol new file mode 100644 index 0000000..a7a0689 --- /dev/null +++ b/contracts/L1/FeeConfig.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; + +import {PRECISION} from "@solarity/solidity-lib/utils/Globals.sol"; + +import {IFeeConfig} from "../interfaces/L1/IFeeConfig.sol"; + +contract FeeConfig is IFeeConfig, OwnableUpgradeable, UUPSUpgradeable { + address public treasury; + uint256 public baseFee; + + mapping(address => uint256) public fees; + + function __FeeConfig_init(address treasury_, uint256 baseFee_) external initializer { + __Ownable_init(); + __UUPSUpgradeable_init(); + + treasury = treasury_; + baseFee = baseFee_; + } + + function setFee(address sender_, uint256 fee_) external onlyOwner { + require(fee_ <= PRECISION, "FC: invalid fee"); + + fees[sender_] = fee_; + } + + function setTreasury(address treasury_) external onlyOwner { + require(treasury_ != address(0), "FC: invalid treasury"); + + treasury = treasury_; + } + + function setBaseFee(uint256 baseFee_) external onlyOwner { + require(baseFee_ < PRECISION, "FC: invalid base fee"); + + baseFee = baseFee_; + } + + function getFeeAndTreasury(address sender_) external view returns (uint256, address) { + uint256 fee_ = fees[sender_]; + if (fee_ == 0) { + fee_ = baseFee; + } + + return (fee_, treasury); + } + + function _authorizeUpgrade(address) internal override onlyOwner {} +} diff --git a/contracts/L1/L1Factory.sol b/contracts/L1/L1Factory.sol new file mode 100644 index 0000000..2385512 --- /dev/null +++ b/contracts/L1/L1Factory.sol @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {IDistribution} from "../interfaces/L1/IDistribution.sol"; +import {IL1Factory} from "../interfaces/L1/IL1Factory.sol"; +import {IL1Sender} from "../interfaces/L1/IL1Sender.sol"; +import {IOwnable} from "../interfaces/IOwnable.sol"; + +import {Factory} from "../Factory.sol"; + +contract L1Factory is IL1Factory, Factory { + address public feeConfig; + + DepositTokenExternalDeps public depositTokenExternalDeps; + ArbExternalDeps public arbExternalDeps; + LzExternalDeps public lzExternalDeps; + + constructor() { + _disableInitializers(); + } + + function L1Factory_init() external initializer { + __Pausable_init(); + __Ownable_init(); + __UUPSUpgradeable_init(); + __Factory_init(); + } + + function setDepositTokenExternalDeps( + DepositTokenExternalDeps calldata depositTokenExternalDeps_ + ) external onlyOwner { + require(depositTokenExternalDeps_.token != address(0), "L1F: invalid token"); + require(depositTokenExternalDeps_.wToken != address(0), "L1F: invalid wtoken"); + + depositTokenExternalDeps = depositTokenExternalDeps_; + } + + function setLzExternalDeps(LzExternalDeps calldata lzExternalDeps_) external onlyOwner { + require(lzExternalDeps_.endpoint != address(0), "L1F: invalid LZ endpoint"); + require(lzExternalDeps_.destinationChainId != 0, "L1F: invalid chain ID"); + + lzExternalDeps = lzExternalDeps_; + } + + function setArbExternalDeps(ArbExternalDeps calldata arbExternalDeps_) external onlyOwner { + require(arbExternalDeps_.endpoint != address(0), "L1F: invalid ARB endpoint"); + + arbExternalDeps = arbExternalDeps_; + } + + function setFeeConfig(address feeConfig_) external onlyOwner { + require(feeConfig_ != address(0), "L1F: invalid fee config"); + + feeConfig = feeConfig_; + } + + function deploy(L1Params calldata l1Params_) external whenNotPaused { + address distributionProxy_ = _deploy2(uint8(PoolType.DISTRIBUTION), l1Params_.protocolName); + address l1SenderProxy_ = _deploy2(uint8(PoolType.L1_SENDER), l1Params_.protocolName); + + IDistribution(distributionProxy_).Distribution_init( + depositTokenExternalDeps.token, + l1SenderProxy_, + feeConfig, + l1Params_.poolsInfo + ); + + IL1Sender.RewardTokenConfig memory lzConfig_ = IL1Sender.RewardTokenConfig( + lzExternalDeps.endpoint, + l1Params_.l2MessageReceiver, + lzExternalDeps.destinationChainId, + lzExternalDeps.zroPaymentAddress, + lzExternalDeps.adapterParams + ); + + IL1Sender.DepositTokenConfig memory arbConfig_ = IL1Sender.DepositTokenConfig( + depositTokenExternalDeps.wToken, + arbExternalDeps.endpoint, + l1Params_.l2TokenReceiver + ); + + IL1Sender(l1SenderProxy_).L1Sender__init(distributionProxy_, lzConfig_, arbConfig_); + + if (l1Params_.isNotUpgradeable) { + IDistribution(distributionProxy_).removeUpgradeability(); + } + + IOwnable(distributionProxy_).transferOwnership(_msgSender()); + IOwnable(l1SenderProxy_).transferOwnership(_msgSender()); + } + + function predictAddresses( + string calldata poolName_, + address sender_ + ) external view returns (address distribution_, address l1Sender_) { + distribution_ = _predictPoolAddress(uint8(PoolType.DISTRIBUTION), poolName_, sender_); + + l1Sender_ = _predictPoolAddress(uint8(PoolType.L1_SENDER), poolName_, sender_); + } +} diff --git a/contracts/L1/L1Sender.sol b/contracts/L1/L1Sender.sol index d3cb2e9..f801745 100644 --- a/contracts/L1/L1Sender.sol +++ b/contracts/L1/L1Sender.sol @@ -10,7 +10,7 @@ import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/U import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import {IWStETH} from "../interfaces/tokens/IWStETH.sol"; -import {IL1Sender, IERC165} from "../interfaces/IL1Sender.sol"; +import {IL1Sender, IERC165} from "../interfaces/L1/IL1Sender.sol"; contract L1Sender is IL1Sender, OwnableUpgradeable, UUPSUpgradeable { address public unwrappedDepositToken; @@ -36,67 +36,40 @@ contract L1Sender is IL1Sender, OwnableUpgradeable, UUPSUpgradeable { __Ownable_init(); __UUPSUpgradeable_init(); - setDistribution(distribution_); - setRewardTokenConfig(rewardTokenConfig_); - setDepositTokenConfig(depositTokenConfig_); + distribution = distribution_; + rewardTokenConfig = rewardTokenConfig_; + _setDepositTokenConfig(depositTokenConfig_); } function supportsInterface(bytes4 interfaceId_) external pure returns (bool) { return interfaceId_ == type(IL1Sender).interfaceId || interfaceId_ == type(IERC165).interfaceId; } - function setDistribution(address distribution_) public onlyOwner { - distribution = distribution_; - } - - function setRewardTokenConfig(RewardTokenConfig calldata newConfig_) public onlyOwner { - rewardTokenConfig = newConfig_; + function setRewardTokenLZParams(address zroPaymentAddress_, bytes calldata adapterParams_) external onlyOwner { + rewardTokenConfig.zroPaymentAddress = zroPaymentAddress_; + rewardTokenConfig.adapterParams = adapterParams_; } - function setDepositTokenConfig(DepositTokenConfig calldata newConfig_) public onlyOwner { + function _setDepositTokenConfig(DepositTokenConfig calldata newConfig_) private { require(newConfig_.receiver != address(0), "L1S: invalid receiver"); - DepositTokenConfig storage oldConfig = depositTokenConfig; - - _replaceDepositToken(oldConfig.token, newConfig_.token); - _replaceDepositTokenGateway(oldConfig.gateway, newConfig_.gateway, oldConfig.token, newConfig_.token); + _setDepositToken(newConfig_.token); + _setDepositTokenGateway(newConfig_.gateway, newConfig_.token); depositTokenConfig = newConfig_; } - function _replaceDepositToken(address oldToken_, address newToken_) private { - bool isTokenChanged_ = oldToken_ != newToken_; - - if (oldToken_ != address(0) && isTokenChanged_) { - // Remove allowance from stETH to wstETH - IERC20(unwrappedDepositToken).approve(oldToken_, 0); - } - - if (isTokenChanged_) { - // Get stETH from wstETH - address unwrappedToken_ = IWStETH(newToken_).stETH(); - // Increase allowance from stETH to wstETH. To exchange stETH for wstETH - IERC20(unwrappedToken_).approve(newToken_, type(uint256).max); + function _setDepositToken(address newToken_) private { + // Get stETH from wstETH + address unwrappedToken_ = IWStETH(newToken_).stETH(); + // Increase allowance from stETH to wstETH. To exchange stETH for wstETH + IERC20(unwrappedToken_).approve(newToken_, type(uint256).max); - unwrappedDepositToken = unwrappedToken_; - } + unwrappedDepositToken = unwrappedToken_; } - function _replaceDepositTokenGateway( - address oldGateway_, - address newGateway_, - address oldToken_, - address newToken_ - ) private { - bool isAllowedChanged_ = (oldToken_ != newToken_) || (oldGateway_ != newGateway_); - - if (oldGateway_ != address(0) && isAllowedChanged_) { - IERC20(oldToken_).approve(IGatewayRouter(oldGateway_).getGateway(oldToken_), 0); - } - - if (isAllowedChanged_) { - IERC20(newToken_).approve(IGatewayRouter(newGateway_).getGateway(newToken_), type(uint256).max); - } + function _setDepositTokenGateway(address newGateway_, address newToken_) private { + IERC20(newToken_).approve(IGatewayRouter(newGateway_).getGateway(newToken_), type(uint256).max); } function sendDepositToken( diff --git a/contracts/L2/L2Factory.sol b/contracts/L2/L2Factory.sol new file mode 100644 index 0000000..a600c71 --- /dev/null +++ b/contracts/L2/L2Factory.sol @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {IL2MessageReceiver} from "../interfaces/L2/IL2MessageReceiver.sol"; +import {IL2TokenReceiver} from "../interfaces/L2/IL2TokenReceiver.sol"; +import {IL2Factory} from "../interfaces/L2/IL2Factory.sol"; +import {IOwnable} from "../interfaces/IOwnable.sol"; +import {IMOR20} from "../interfaces/L2/IMOR20.sol"; + +import {Factory} from "../Factory.sol"; +import {MOR20Deployer} from "../libs/MOR20Deployer.sol"; + +contract L2Factory is IL2Factory, Factory { + UniswapExternalDeps public uniswapExternalDeps; + LzExternalDeps public lzExternalDeps; + + constructor() { + _disableInitializers(); + } + + function L2Factory_init() external initializer { + __Pausable_init(); + __Ownable_init(); + __UUPSUpgradeable_init(); + __Factory_init(); + } + + function setLzExternalDeps(LzExternalDeps calldata lzExternalDeps_) external onlyOwner { + require(lzExternalDeps_.endpoint != address(0), "L2F: invalid LZ endpoint"); + require(lzExternalDeps_.oftEndpoint != address(0), "L2F: invalid LZ OFT endpoint"); + require(lzExternalDeps_.senderChainId != 0, "L2F: invalid chain ID"); + + lzExternalDeps = lzExternalDeps_; + } + + function setUniswapExternalDeps(UniswapExternalDeps calldata uniswapExternalDeps_) external onlyOwner { + require(uniswapExternalDeps_.router != address(0), "L2F: invalid UNI router"); + require(uniswapExternalDeps_.nonfungiblePositionManager != address(0), "L2F: invalid NPM"); + + uniswapExternalDeps = uniswapExternalDeps_; + } + + function deploy(L2Params calldata l2Params_) external whenNotPaused { + address l2MessageReceiver = _deploy2(uint8(PoolType.L2_MESSAGE_RECEIVER), l2Params_.protocolName); + address l2TokenReceiver = _deploy2(uint8(PoolType.L2_TOKEN_RECEIVER), l2Params_.protocolName); + + address mor20 = MOR20Deployer.deployMOR20( + l2Params_.mor20Name, + l2Params_.mor20Symbol, + lzExternalDeps.oftEndpoint, + _msgSender(), + l2MessageReceiver + ); + deployedProxies[_msgSender()][l2Params_.protocolName][uint8(PoolType.MOR20)] = mor20; + + IL2MessageReceiver(l2MessageReceiver).L2MessageReceiver__init( + mor20, + IL2MessageReceiver.Config(lzExternalDeps.endpoint, l2Params_.l1Sender, lzExternalDeps.senderChainId) + ); + + IL2TokenReceiver(l2TokenReceiver).L2TokenReceiver__init( + uniswapExternalDeps.router, + uniswapExternalDeps.nonfungiblePositionManager, + l2Params_.firstSwapParams_, + IL2TokenReceiver.SwapParams(l2Params_.firstSwapParams_.tokenOut, mor20, l2Params_.secondSwapFee) + ); + + IOwnable(l2MessageReceiver).transferOwnership(_msgSender()); + IOwnable(l2TokenReceiver).transferOwnership(_msgSender()); + } + + function predictAddresses( + string calldata poolName_, + address sender_ + ) external view returns (address l2MessageReceiver_, address l2TokenReceiver_) { + l2MessageReceiver_ = _predictPoolAddress(uint8(PoolType.L2_MESSAGE_RECEIVER), poolName_, sender_); + + l2TokenReceiver_ = _predictPoolAddress(uint8(PoolType.L2_TOKEN_RECEIVER), poolName_, sender_); + } +} diff --git a/contracts/L2/L2MessageReceiver.sol b/contracts/L2/L2MessageReceiver.sol index 6e9d831..9825669 100644 --- a/contracts/L2/L2MessageReceiver.sol +++ b/contracts/L2/L2MessageReceiver.sol @@ -4,8 +4,8 @@ pragma solidity ^0.8.20; import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; -import {IMOR} from "../interfaces/IMOR.sol"; -import {IL2MessageReceiver} from "../interfaces/IL2MessageReceiver.sol"; +import {IMOR20} from "../interfaces/L2/IMOR20.sol"; +import {IL2MessageReceiver} from "../interfaces/L2/IL2MessageReceiver.sol"; contract L2MessageReceiver is IL2MessageReceiver, OwnableUpgradeable, UUPSUpgradeable { address public rewardToken; @@ -18,16 +18,20 @@ contract L2MessageReceiver is IL2MessageReceiver, OwnableUpgradeable, UUPSUpgrad _disableInitializers(); } - function L2MessageReceiver__init() external initializer { + function L2MessageReceiver__init(address rewardToken_, Config calldata config_) external initializer { __Ownable_init(); __UUPSUpgradeable_init(); - } - function setParams(address rewardToken_, Config calldata config_) external onlyOwner { rewardToken = rewardToken_; config = config_; } + function setLzSender(address lzSender_) external onlyOwner { + require(lzSender_ != address(0), "L2MR: invalid sender"); + + config.sender = lzSender_; + } + function lzReceive( uint16 senderChainId_, bytes memory senderAndReceiverAddresses_, @@ -59,10 +63,10 @@ contract L2MessageReceiver is IL2MessageReceiver, OwnableUpgradeable, UUPSUpgrad require(payloadHash_ != bytes32(0), "L2MR: no stored message"); require(keccak256(payload_) == payloadHash_, "L2MR: invalid payload"); - _nonblockingLzReceive(senderChainId_, senderAndReceiverAddresses_, payload_); - delete failedMessages[senderChainId_][senderAndReceiverAddresses_][nonce_]; + _nonblockingLzReceive(senderChainId_, senderAndReceiverAddresses_, payload_); + emit RetryMessageSuccess(senderChainId_, senderAndReceiverAddresses_, nonce_, payload_); } @@ -102,7 +106,7 @@ contract L2MessageReceiver is IL2MessageReceiver, OwnableUpgradeable, UUPSUpgrad (address user_, uint256 amount_) = abi.decode(payload_, (address, uint256)); - IMOR(rewardToken).mint(user_, amount_); + IMOR20(rewardToken).mint(user_, amount_); } function _authorizeUpgrade(address) internal view override onlyOwner {} diff --git a/contracts/L2/L2TokenReceiver.sol b/contracts/L2/L2TokenReceiver.sol index b58b1ac..96a4bb1 100644 --- a/contracts/L2/L2TokenReceiver.sol +++ b/contracts/L2/L2TokenReceiver.sol @@ -1,20 +1,22 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; +import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import {ISwapRouter} from "@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol"; import {TransferHelper} from "@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol"; -import {IL2TokenReceiver, IERC165, IERC721Receiver} from "../interfaces/IL2TokenReceiver.sol"; +import {IL2TokenReceiver, IERC165, IERC721Receiver} from "../interfaces/L2/IL2TokenReceiver.sol"; import {INonfungiblePositionManager} from "../interfaces/uniswap-v3/INonfungiblePositionManager.sol"; contract L2TokenReceiver is IL2TokenReceiver, OwnableUpgradeable, UUPSUpgradeable { address public router; address public nonfungiblePositionManager; - SwapParams public params; + SwapParams public firstSwapParams; + SwapParams public secondSwapParams; constructor() { _disableInitializers(); @@ -23,7 +25,8 @@ contract L2TokenReceiver is IL2TokenReceiver, OwnableUpgradeable, UUPSUpgradeabl function L2TokenReceiver__init( address router_, address nonfungiblePositionManager_, - SwapParams memory params_ + SwapParams memory firstSwapParams_, + SwapParams memory secondSwapParams_ ) external initializer { __Ownable_init(); __UUPSUpgradeable_init(); @@ -31,7 +34,8 @@ contract L2TokenReceiver is IL2TokenReceiver, OwnableUpgradeable, UUPSUpgradeabl router = router_; nonfungiblePositionManager = nonfungiblePositionManager_; - _editParams(params_); + _addAllowanceUpdateSwapParams(firstSwapParams_, true); + _addAllowanceUpdateSwapParams(secondSwapParams_, false); } function supportsInterface(bytes4 interfaceId_) external pure returns (bool) { @@ -41,25 +45,37 @@ contract L2TokenReceiver is IL2TokenReceiver, OwnableUpgradeable, UUPSUpgradeabl interfaceId_ == type(IERC165).interfaceId; } - function editParams(SwapParams memory newParams_) external onlyOwner { - if (params.tokenIn != newParams_.tokenIn) { - TransferHelper.safeApprove(params.tokenIn, router, 0); - TransferHelper.safeApprove(params.tokenIn, nonfungiblePositionManager, 0); + function editParams(SwapParams memory newParams_, bool isEditFirstParams_) external onlyOwner { + SwapParams memory params_ = _getSwapParams(isEditFirstParams_); + + if (params_.tokenIn != address(0) && params_.tokenIn != newParams_.tokenIn) { + TransferHelper.safeApprove(params_.tokenIn, router, 0); + TransferHelper.safeApprove(params_.tokenIn, nonfungiblePositionManager, 0); } - if (params.tokenOut != newParams_.tokenOut) { - TransferHelper.safeApprove(params.tokenOut, nonfungiblePositionManager, 0); + if (params_.tokenOut != address(0) && params_.tokenOut != newParams_.tokenOut) { + TransferHelper.safeApprove(params_.tokenOut, nonfungiblePositionManager, 0); } - _editParams(newParams_); + _addAllowanceUpdateSwapParams(newParams_, isEditFirstParams_); + } + + function withdrawToken(address recipient_, address token_, uint256 amount_) external onlyOwner { + TransferHelper.safeTransfer(token_, recipient_, amount_); + } + + function withdrawTokenId(address recipient_, address token_, uint256 tokenId_) external onlyOwner { + IERC721(token_).safeTransferFrom(address(this), recipient_, tokenId_); } function swap( uint256 amountIn_, uint256 amountOutMinimum_, - uint256 deadline_ + uint256 deadline_, + uint160 sqrtPriceLimitX96_, + bool isUseFirstSwapParams_ ) external onlyOwner returns (uint256) { - SwapParams memory params_ = params; + SwapParams memory params_ = _getSwapParams(isUseFirstSwapParams_); ISwapRouter.ExactInputSingleParams memory swapParams_ = ISwapRouter.ExactInputSingleParams({ tokenIn: params_.tokenIn, @@ -69,7 +85,7 @@ contract L2TokenReceiver is IL2TokenReceiver, OwnableUpgradeable, UUPSUpgradeabl deadline: deadline_, amountIn: amountIn_, amountOutMinimum: amountOutMinimum_, - sqrtPriceLimitX96: params_.sqrtPriceLimitX96 + sqrtPriceLimitX96: sqrtPriceLimitX96_ }); uint256 amountOut_ = ISwapRouter(router).exactInputSingle(swapParams_); @@ -81,38 +97,18 @@ contract L2TokenReceiver is IL2TokenReceiver, OwnableUpgradeable, UUPSUpgradeabl function increaseLiquidityCurrentRange( uint256 tokenId_, - uint256 depositTokenAmountAdd_, - uint256 rewardTokenAmountAdd_, - uint256 depositTokenAmountMin_, - uint256 rewardTokenAmountMin_ + uint256 amount0Add_, + uint256 amount1Add_, + uint256 amount0Min_, + uint256 amount1Min_ ) external onlyOwner returns (uint128 liquidity_, uint256 amount0_, uint256 amount1_) { - uint256 amountAdd0_; - uint256 amountAdd1_; - uint256 amountMin0_; - uint256 amountMin1_; - - (, , address token0_, , , , , , , , , ) = INonfungiblePositionManager(nonfungiblePositionManager).positions( - tokenId_ - ); - if (token0_ == params.tokenIn) { - amountAdd0_ = depositTokenAmountAdd_; - amountAdd1_ = rewardTokenAmountAdd_; - amountMin0_ = depositTokenAmountMin_; - amountMin1_ = rewardTokenAmountMin_; - } else { - amountAdd0_ = rewardTokenAmountAdd_; - amountAdd1_ = depositTokenAmountAdd_; - amountMin0_ = rewardTokenAmountMin_; - amountMin1_ = depositTokenAmountMin_; - } - INonfungiblePositionManager.IncreaseLiquidityParams memory params_ = INonfungiblePositionManager .IncreaseLiquidityParams({ tokenId: tokenId_, - amount0Desired: amountAdd0_, - amount1Desired: amountAdd1_, - amount0Min: amountMin0_, - amount1Min: amountMin1_, + amount0Desired: amount0Add_, + amount1Desired: amount1Add_, + amount0Min: amount0Min_, + amount1Min: amount1Min_, deadline: block.timestamp }); @@ -120,10 +116,32 @@ contract L2TokenReceiver is IL2TokenReceiver, OwnableUpgradeable, UUPSUpgradeabl params_ ); - emit LiquidityIncreased(tokenId_, amount0_, amount1_, liquidity_, amountMin0_, amountMin1_); + emit LiquidityIncreased(tokenId_, amount0_, amount1_, liquidity_, amount0Min_, amount1Min_); } - function collectFees(uint256 tokenId_) external returns (uint256 amount0_, uint256 amount1_) { + function decreaseLiquidityCurrentRange( + uint256 tokenId_, + uint128 liquidity_, + uint256 amount0Min_, + uint256 amount1Min_ + ) external onlyOwner returns (uint256 amount0_, uint256 amount1_) { + INonfungiblePositionManager.DecreaseLiquidityParams memory params_ = INonfungiblePositionManager + .DecreaseLiquidityParams({ + tokenId: tokenId_, + liquidity: liquidity_, + amount0Min: amount0Min_, + amount1Min: amount1Min_, + deadline: block.timestamp + }); + + (amount0_, amount1_) = INonfungiblePositionManager(nonfungiblePositionManager).decreaseLiquidity(params_); + + collectFees(tokenId_); + + emit LiquidityDecreased(tokenId_, amount0_, amount1_, liquidity_, amount0Min_, amount1Min_); + } + + function collectFees(uint256 tokenId_) public returns (uint256 amount0_, uint256 amount1_) { INonfungiblePositionManager.CollectParams memory params_ = INonfungiblePositionManager.CollectParams({ tokenId: tokenId_, recipient: address(this), @@ -140,7 +158,7 @@ contract L2TokenReceiver is IL2TokenReceiver, OwnableUpgradeable, UUPSUpgradeabl return this.onERC721Received.selector; } - function _editParams(SwapParams memory newParams_) private { + function _addAllowanceUpdateSwapParams(SwapParams memory newParams_, bool isEditFirstParams_) private { require(newParams_.tokenIn != address(0), "L2TR: invalid tokenIn"); require(newParams_.tokenOut != address(0), "L2TR: invalid tokenOut"); @@ -149,7 +167,15 @@ contract L2TokenReceiver is IL2TokenReceiver, OwnableUpgradeable, UUPSUpgradeabl TransferHelper.safeApprove(newParams_.tokenOut, nonfungiblePositionManager, type(uint256).max); - params = newParams_; + if (isEditFirstParams_) { + firstSwapParams = newParams_; + } else { + secondSwapParams = newParams_; + } + } + + function _getSwapParams(bool isUseFirstSwapParams_) internal view returns (SwapParams memory) { + return isUseFirstSwapParams_ ? firstSwapParams : secondSwapParams; } function _authorizeUpgrade(address) internal view override onlyOwner {} diff --git a/contracts/L2/L2TokenReceiverV2.sol b/contracts/L2/L2TokenReceiverV2.sol deleted file mode 100644 index d0e83ab..0000000 --- a/contracts/L2/L2TokenReceiverV2.sol +++ /dev/null @@ -1,183 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; -import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; -import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; - -import {ISwapRouter} from "@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol"; -import {TransferHelper} from "@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol"; - -import {IL2TokenReceiverV2, IERC165, IERC721Receiver} from "../interfaces/IL2TokenReceiverV2.sol"; -import {INonfungiblePositionManager} from "../interfaces/uniswap-v3/INonfungiblePositionManager.sol"; - -contract L2TokenReceiverV2 is IL2TokenReceiverV2, OwnableUpgradeable, UUPSUpgradeable { - address public router; - address public nonfungiblePositionManager; - - SwapParams public secondSwapParams; - - // Storage changes for L2TokenReceiverV2 - SwapParams public firstSwapParams; - - constructor() { - _disableInitializers(); - } - - function L2TokenReceiver__init( - address router_, - address nonfungiblePositionManager_, - SwapParams memory params_ - ) external initializer { - __Ownable_init(); - __UUPSUpgradeable_init(); - - router = router_; - nonfungiblePositionManager = nonfungiblePositionManager_; - - _addAllowanceUpdateSwapParams(params_, false); - } - - function supportsInterface(bytes4 interfaceId_) external pure returns (bool) { - return - interfaceId_ == type(IL2TokenReceiverV2).interfaceId || - interfaceId_ == type(IERC721Receiver).interfaceId || - interfaceId_ == type(IERC165).interfaceId; - } - - function editParams(SwapParams memory newParams_, bool isEditFirstParams_) external onlyOwner { - SwapParams memory params_ = _getSwapParams(isEditFirstParams_); - - if (params_.tokenIn != address(0) && params_.tokenIn != newParams_.tokenIn) { - TransferHelper.safeApprove(params_.tokenIn, router, 0); - TransferHelper.safeApprove(params_.tokenIn, nonfungiblePositionManager, 0); - } - - if (params_.tokenOut != address(0) && params_.tokenOut != newParams_.tokenOut) { - TransferHelper.safeApprove(params_.tokenOut, nonfungiblePositionManager, 0); - } - - _addAllowanceUpdateSwapParams(newParams_, isEditFirstParams_); - } - - function withdrawToken(address recipient_, address token_, uint256 amount_) external onlyOwner { - TransferHelper.safeTransfer(token_, recipient_, amount_); - } - - function withdrawTokenId(address recipient_, address token_, uint256 tokenId_) external onlyOwner { - IERC721(token_).safeTransferFrom(address(this), recipient_, tokenId_); - } - - function swap( - uint256 amountIn_, - uint256 amountOutMinimum_, - uint256 deadline_, - bool isEditFirstParams_ - ) external onlyOwner returns (uint256) { - SwapParams memory params_ = _getSwapParams(isEditFirstParams_); - - ISwapRouter.ExactInputSingleParams memory swapParams_ = ISwapRouter.ExactInputSingleParams({ - tokenIn: params_.tokenIn, - tokenOut: params_.tokenOut, - fee: params_.fee, - recipient: address(this), - deadline: deadline_, - amountIn: amountIn_, - amountOutMinimum: amountOutMinimum_, - sqrtPriceLimitX96: params_.sqrtPriceLimitX96 - }); - - uint256 amountOut_ = ISwapRouter(router).exactInputSingle(swapParams_); - - emit TokensSwapped(params_.tokenIn, params_.tokenOut, amountIn_, amountOut_, amountOutMinimum_); - - return amountOut_; - } - - function increaseLiquidityCurrentRange( - uint256 tokenId_, - uint256 depositTokenAmountAdd_, - uint256 rewardTokenAmountAdd_, - uint256 depositTokenAmountMin_, - uint256 rewardTokenAmountMin_ - ) external onlyOwner returns (uint128 liquidity_, uint256 amount0_, uint256 amount1_) { - uint256 amountAdd0_; - uint256 amountAdd1_; - uint256 amountMin0_; - uint256 amountMin1_; - - (, , address token0_, , , , , , , , , ) = INonfungiblePositionManager(nonfungiblePositionManager).positions( - tokenId_ - ); - if (token0_ == secondSwapParams.tokenIn) { - amountAdd0_ = depositTokenAmountAdd_; - amountAdd1_ = rewardTokenAmountAdd_; - amountMin0_ = depositTokenAmountMin_; - amountMin1_ = rewardTokenAmountMin_; - } else { - amountAdd0_ = rewardTokenAmountAdd_; - amountAdd1_ = depositTokenAmountAdd_; - amountMin0_ = rewardTokenAmountMin_; - amountMin1_ = depositTokenAmountMin_; - } - - INonfungiblePositionManager.IncreaseLiquidityParams memory params_ = INonfungiblePositionManager - .IncreaseLiquidityParams({ - tokenId: tokenId_, - amount0Desired: amountAdd0_, - amount1Desired: amountAdd1_, - amount0Min: amountMin0_, - amount1Min: amountMin1_, - deadline: block.timestamp - }); - - (liquidity_, amount0_, amount1_) = INonfungiblePositionManager(nonfungiblePositionManager).increaseLiquidity( - params_ - ); - - emit LiquidityIncreased(tokenId_, amount0_, amount1_, liquidity_, amountMin0_, amountMin1_); - } - - function collectFees(uint256 tokenId_) external returns (uint256 amount0_, uint256 amount1_) { - INonfungiblePositionManager.CollectParams memory params_ = INonfungiblePositionManager.CollectParams({ - tokenId: tokenId_, - recipient: address(this), - amount0Max: type(uint128).max, - amount1Max: type(uint128).max - }); - - (amount0_, amount1_) = INonfungiblePositionManager(nonfungiblePositionManager).collect(params_); - - emit FeesCollected(tokenId_, amount0_, amount1_); - } - - function version() external pure returns (uint256) { - return 2; - } - - function onERC721Received(address, address, uint256, bytes calldata) external pure returns (bytes4) { - return this.onERC721Received.selector; - } - - function _addAllowanceUpdateSwapParams(SwapParams memory newParams_, bool isEditFirstParams_) private { - require(newParams_.tokenIn != address(0), "L2TR: invalid tokenIn"); - require(newParams_.tokenOut != address(0), "L2TR: invalid tokenOut"); - - TransferHelper.safeApprove(newParams_.tokenIn, router, type(uint256).max); - TransferHelper.safeApprove(newParams_.tokenIn, nonfungiblePositionManager, type(uint256).max); - - TransferHelper.safeApprove(newParams_.tokenOut, nonfungiblePositionManager, type(uint256).max); - - if (isEditFirstParams_) { - firstSwapParams = newParams_; - } else { - secondSwapParams = newParams_; - } - } - - function _getSwapParams(bool isEditFirstParams_) internal view returns (SwapParams memory) { - return isEditFirstParams_ ? firstSwapParams : secondSwapParams; - } - - function _authorizeUpgrade(address) internal view override onlyOwner {} -} diff --git a/contracts/L2/MOR20.sol b/contracts/L2/MOR20.sol new file mode 100644 index 0000000..da0f46f --- /dev/null +++ b/contracts/L2/MOR20.sol @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {OFT} from "../@layerzerolabs/lz-evm-oapp-v2/contracts/oft/OFT.sol"; + +import {IMOR20, IERC20, IERC165, IOAppCore} from "../interfaces/L2/IMOR20.sol"; + +contract MOR20 is IMOR20, OFT { + mapping(address => bool) public isMinter; + + constructor( + string memory name_, + string memory symbol_, + address layerZeroEndpoint_, + address delegate_, + address minter_ + ) OFT(name_, symbol_, layerZeroEndpoint_, delegate_) { + require(minter_ != address(0), "MOR20: invalid minter"); + + isMinter[minter_] = true; + + transferOwnership(delegate_); + } + + /** + * @dev Returns true if this contract implements the interface defined by + * `interfaceId`. See the corresponding + * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] + * to learn more about how these ids are created. + * + * This function call must use less than 30 000 gas. + */ + function supportsInterface(bytes4 interfaceId_) external pure returns (bool) { + return + interfaceId_ == type(IMOR20).interfaceId || + interfaceId_ == type(IERC20).interfaceId || + interfaceId_ == type(IOAppCore).interfaceId || + interfaceId_ == type(IERC165).interfaceId; + } + + /** + * @notice The function update `minter` addresses. + * @param minter_ The upadted minter address. + * @param status_ The new status. True or false. + */ + function updateMinter(address minter_, bool status_) external onlyOwner { + isMinter[minter_] = status_; + } + + /** + * @notice The function to mint tokens. + * @param account_ The address of the account to mint tokens to. + * @param amount_ The amount of tokens to mint. + */ + function mint(address account_, uint256 amount_) public { + require(isMinter[_msgSender()], "MOR20: invalid caller"); + + _mint(account_, amount_); + } + + /** + * @notice The function to destroys `amount` tokens from the caller. + * See {ERC20-_burn}. + * @param amount_ The amount of tokens to burn. + */ + function burn(uint256 amount_) public { + _burn(_msgSender(), amount_); + } + + /** + * @notice The function to destroys `amount` tokens from `account`, deducting from the caller's + * allowance. + * + * See {ERC20-_burn} and {ERC20-allowance}. + * + * Requirements: + * + * - the caller must have allowance for ``accounts``'s tokens of at least + * `amount`. + * + * @param account_ The address of the account to burn tokens from. + * @param amount_ The amount of tokens to burn. + */ + function burnFrom(address account_, uint256 amount_) public { + _spendAllowance(account_, _msgSender(), amount_); + _burn(account_, amount_); + } +} diff --git a/contracts/L2/MOROFT.sol b/contracts/L2/MOROFT.sol deleted file mode 100644 index b7cacae..0000000 --- a/contracts/L2/MOROFT.sol +++ /dev/null @@ -1,49 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import {OFT} from "../@layerzerolabs/lz-evm-oapp-v2/contracts/oft/OFT.sol"; - -import {IMOROFT, IERC20, IERC165, IOAppCore} from "../interfaces/IMOROFT.sol"; - -contract MOROFT is IMOROFT, OFT { - address private immutable _minter; - - constructor( - address layerZeroEndpoint_, - address delegate_, - address minter_ - ) OFT("MOR", "MOR", layerZeroEndpoint_, delegate_) { - require(minter_ != address(0), "MOROFT: invalid minter"); - - _minter = minter_; - - transferOwnership(delegate_); - } - - function supportsInterface(bytes4 interfaceId_) external pure returns (bool) { - return - interfaceId_ == type(IMOROFT).interfaceId || - interfaceId_ == type(IERC20).interfaceId || - interfaceId_ == type(IOAppCore).interfaceId || - interfaceId_ == type(IERC165).interfaceId; - } - - function minter() public view returns (address) { - return _minter; - } - - function mint(address account_, uint256 amount_) public { - require(_msgSender() == minter(), "MOROFT: invalid caller"); - - _mint(account_, amount_); - } - - function burn(uint256 amount_) public { - _burn(_msgSender(), amount_); - } - - function burnFrom(address account_, uint256 amount_) public { - _spendAllowance(account_, _msgSender(), amount_); - _burn(account_, amount_); - } -} diff --git a/contracts/interfaces/IFactory.sol b/contracts/interfaces/IFactory.sol new file mode 100644 index 0000000..c693972 --- /dev/null +++ b/contracts/interfaces/IFactory.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +interface IFactory { + /** + * The event is emitted when the proxy is deployed. + * @param proxy The proxy address. + * @param implementation The implementation. + * @param poolType The `poolType`. + * @param poolName The `poolName`. + */ + event ProxyDeployed(address proxy, address indexed implementation, uint8 indexed poolType, string poolName); + + /** + * The function to set the implementation for the specific pool. + * @param poolType_ The type of the pool. + * @param implementation_ The implementation the pool will point to. + */ + function setImplementation(uint8 poolType_, address implementation_) external; + + /** + * The function to get the implementation of the specific pools. + * @param poolType_ The type of the pools. + * @return The implementation which the pool points to. + */ + function getImplementation(uint8 poolType_) external view returns (address); +} diff --git a/contracts/interfaces/IL2TokenReceiver.sol b/contracts/interfaces/IL2TokenReceiver.sol deleted file mode 100644 index 074bfb8..0000000 --- a/contracts/interfaces/IL2TokenReceiver.sol +++ /dev/null @@ -1,112 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; -import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; - -/** - * This is Swap contract that swaps tokens using Uniswap V3. - */ -interface IL2TokenReceiver is IERC165, IERC721Receiver { - /** - * The structure that stores the swap params. - * @param tokenIn The address of the token to swap from. - * @param tokenOut The address of the token to swap to. - * @param fee The fee of the swap. - * @param sqrtPriceLimitX96 The price limit of the swap. - */ - struct SwapParams { - address tokenIn; - address tokenOut; - uint24 fee; - uint160 sqrtPriceLimitX96; - } - - /** - * The event that is emitted when the swap is executed. - * @param tokenIn The address of the token to swap from. - * @param tokenOut The address of the token to swap to. - * @param amountIn The amount of tokens to swap. - * @param amountOut The amount of tokens received. - * @param amountOutMinimum The minimum amount of tokens to receive. - */ - event TokensSwapped( - address indexed tokenIn, - address indexed tokenOut, - uint256 amountIn, - uint256 amountOut, - uint256 amountOutMinimum - ); - - /** - * The event that is emitted when the liquidity is increased. - * @param tokenId The ID of the position. - * @param amount0 The amount of token0 added. - * @param amount1 The amount of token1 added. - * @param liquidity The amount of liquidity added. - * @param amount0Min The minimum amount of token0 to add. - * @param amount1Min The minimum amount of token1 to add. - */ - event LiquidityIncreased( - uint256 indexed tokenId, - uint256 amount0, - uint256 amount1, - uint256 liquidity, - uint256 amount0Min, - uint256 amount1Min - ); - - /** - * The event that is emitted when the fees are collected. - * @param tokenId The ID of the position. - * @param amount0 The amount of token0 collected. - * @param amount1 The amount of token1 collected. - */ - event FeesCollected(uint256 indexed tokenId, uint256 amount0, uint256 amount1); - - /** - * The function to edit the swap params. - * @param params_ The new swap params. - */ - function editParams(SwapParams memory params_) external; - - /** - * The function to swap current contract's tokens specified in the params. - * @param amountIn_ The amount of tokens to swap. - * @param amountOutMinimum_ The minimum amount of tokens to receive. - * @param deadline_ The deadline for the swap. - * @return The amount of tokens received. - */ - function swap(uint256 amountIn_, uint256 amountOutMinimum_, uint256 deadline_) external returns (uint256); - - /** - * The function to increase liquidity in the current price range. - * @param tokenId The ID of the position. - * @param amountAdd0_ The amount of tokenIn to add. - * @param amountAdd1_ The amount of tokenOut to add. - * @param depositTokenAmountMin_ The minimum amount of deposit token to add. - * @param rewardTokenAmountMin_ The minimum amount of reward token to add. - * @return liquidity_ The amount of liquidity added. - * @return amount0_ The amount of token0 added. - * @return amount1_ The amount of token1 added. - */ - function increaseLiquidityCurrentRange( - uint256 tokenId, - uint256 amountAdd0_, - uint256 amountAdd1_, - uint256 depositTokenAmountMin_, - uint256 rewardTokenAmountMin_ - ) external returns (uint128 liquidity_, uint256 amount0_, uint256 amount1_); - - /** - * The function to get the router address. - * @return The address of the router. - */ - function router() external view returns (address); - - /** - * The function to get the nonfungible position manager address. - * @return The address of the nonfungible position manager. - */ - function nonfungiblePositionManager() external view returns (address); -} diff --git a/contracts/interfaces/IMOR.sol b/contracts/interfaces/IMOR.sol deleted file mode 100644 index 49f138e..0000000 --- a/contracts/interfaces/IMOR.sol +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; - -/** - * This is the MOR token contract. The token is ERC20 with cap and burnable features. - */ -interface IMOR is IERC20, IERC165 { - /** - * The function to get the cap of the token. - * @return The cap of the token. - */ - function cap() external view returns (uint256); - - /** - * The function to mint tokens. - * @param account_ The address of the account to mint tokens to. - * @param amount_ The amount of tokens to mint. - */ - function mint(address account_, uint256 amount_) external; -} diff --git a/contracts/interfaces/IOwnable.sol b/contracts/interfaces/IOwnable.sol new file mode 100644 index 0000000..32fda58 --- /dev/null +++ b/contracts/interfaces/IOwnable.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +interface IOwnable { + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + * Can only be called by the current owner. + */ + function transferOwnership(address newOwner_) external; +} diff --git a/contracts/interfaces/IDistribution.sol b/contracts/interfaces/L1/IDistribution.sol similarity index 95% rename from contracts/interfaces/IDistribution.sol rename to contracts/interfaces/L1/IDistribution.sol index 762d1f7..ebf81de 100644 --- a/contracts/interfaces/IDistribution.sol +++ b/contracts/interfaces/L1/IDistribution.sol @@ -104,9 +104,15 @@ interface IDistribution { * The function to initialize the contract. * @param depositToken_ The address of deposit token. * @param l1Sender_ The address of bridge contract. + * @param feeConfig_ The address of fee config contract. * @param poolsInfo_ The array of initial pools. */ - function Distribution_init(address depositToken_, address l1Sender_, Pool[] calldata poolsInfo_) external; + function Distribution_init( + address depositToken_, + address l1Sender_, + address feeConfig_, + Pool[] calldata poolsInfo_ + ) external; /** * The function to create a new pool. @@ -187,16 +193,10 @@ interface IDistribution { ) external payable returns (bytes memory); /** - * The function to remove upgradeability. + * The function to remove the upgradeability of the contract. */ function removeUpgradeability() external; - /** - * The function to check if the contract is upgradeable. - * @return The flag that indicates if the contract is upgradeable. - */ - function isNotUpgradeable() external view returns (bool); - /** * The function to get the address of deposit token. * @return The address of deposit token. diff --git a/contracts/interfaces/L1/IFeeConfig.sol b/contracts/interfaces/L1/IFeeConfig.sol new file mode 100644 index 0000000..fdab411 --- /dev/null +++ b/contracts/interfaces/L1/IFeeConfig.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +/** + * This is FeeConfig contract that stores all the fees and treasury data. + */ +interface IFeeConfig { + /** + * The function that initializes the contract. + * @param treasury_ The treasury address. + * @param baseFee_ The base fee. + */ + function __FeeConfig_init(address treasury_, uint256 baseFee_) external; + + /** + * The function that returns the treasury address. + */ + function treasury() external view returns (address); + + /** + * The function that returns the base fee. + */ + function baseFee() external view returns (uint256); + + /** + * The function that returns the fee for the sender. + * @dev If the fee is 0, the base fee is used. + */ + function fees(address sender_) external view returns (uint256); + + /** + * The function that returns the fee and treasury address for the sender. + */ + function getFeeAndTreasury(address sender_) external view returns (uint256, address); + + /** + * The function that sets the fee for the sender. + */ + function setFee(address sender_, uint256 fee_) external; + + /** + * The function that sets the treasury address. + */ + function setTreasury(address treasury_) external; + + /** + * The function that sets the base fee. + */ + function setBaseFee(uint256 baseFee_) external; +} diff --git a/contracts/interfaces/L1/IL1Factory.sol b/contracts/interfaces/L1/IL1Factory.sol new file mode 100644 index 0000000..cd868b3 --- /dev/null +++ b/contracts/interfaces/L1/IL1Factory.sol @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {IDistribution} from "./IDistribution.sol"; + +/** + * This is L1Factory contract that deploys the L1 contracts. + */ +interface IL1Factory { + /** + * The struct that represents the deployed L1 contract. + */ + enum PoolType { + DISTRIBUTION, + L1_SENDER + } + + /** + * The struct that represents the parameters for + * deploying specific L1 contracts. + * @param protocolName The protocol name. + * @param isNotUpgradeable The flag that indicates if the contract is not upgradeable. + * @param poolsInfo The pools information. + * @param l2TokenReceiver The L2 token receiver address. + * @param l2MessageReceiver The L2 message receiver address. + */ + struct L1Params { + string protocolName; + bool isNotUpgradeable; + IDistribution.Pool[] poolsInfo; + address l2TokenReceiver; + address l2MessageReceiver; + } + + /** + * The struct that represents the external dependencies for the deposit token. + * @param token The token address. + * @param wToken The wrapped token address. + */ + struct DepositTokenExternalDeps { + address token; + address wToken; + } + + /** + * The struct that represents the external dependencies for the LZ contract. + * @param endpoint The endpoint address. + * @param zroPaymentAddress The ZRO payment address. + * @param adapterParams The adapter parameters. + * @param destinationChainId The destination chain ID. + */ + struct LzExternalDeps { + address endpoint; + address zroPaymentAddress; + bytes adapterParams; + uint16 destinationChainId; + } + + /** + * The struct that represents the external dependencies for the Arbitrub contract. + * @param endpoint The endpoint address. + */ + struct ArbExternalDeps { + address endpoint; + } + + /** + * The function that initializes the contract. + */ + function L1Factory_init() external; + + /** + * The function to get fee config address. + * @return The fee config address. + */ + function feeConfig() external view returns (address); + + /** + * The function that sets the deposit token external dependencies. + * @param depositTokenExternalDeps_ The deposit token external dependencies. + */ + function setDepositTokenExternalDeps( + IL1Factory.DepositTokenExternalDeps calldata depositTokenExternalDeps_ + ) external; + + /** + * The function that sets the LZ external dependencies. + * @param lzExternalDeps_ The LZ external dependencies. + */ + function setLzExternalDeps(IL1Factory.LzExternalDeps calldata lzExternalDeps_) external; + + /** + * The function that sets the Arbitrum external dependencies. + * @param arbExternalDeps_ The Arbitrum external dependencies. + */ + function setArbExternalDeps(IL1Factory.ArbExternalDeps calldata arbExternalDeps_) external; + + /** + * The function that deploys the L1 contracts. + * @param l1Params_ The L1 parameters. + */ + function deploy(IL1Factory.L1Params calldata l1Params_) external; +} diff --git a/contracts/interfaces/IL1Sender.sol b/contracts/interfaces/L1/IL1Sender.sol similarity index 68% rename from contracts/interfaces/IL1Sender.sol rename to contracts/interfaces/L1/IL1Sender.sol index 47b086e..a7c3f46 100644 --- a/contracts/interfaces/IL1Sender.sol +++ b/contracts/interfaces/L1/IL1Sender.sol @@ -5,7 +5,7 @@ import {IERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; interface IL1Sender is IERC165 { /** - * The structure that stores the deposit token's data. + * The structure that stores the deposit token's data (stETH). * @param token The address of wrapped deposit token. * @param gateway The address of token's gateway. * @param receiver The address of wrapped token's receiver on L2. @@ -33,40 +33,39 @@ interface IL1Sender is IERC165 { } /** - * The function to get the deposit token's address. + * The function to initialize the contract. + * @param distribution_ The address of the distribution contract. + * @param rewardTokenConfig_ The reward token's config. + * @param depositTokenConfig_ The deposit token's config. */ - function unwrappedDepositToken() external view returns (address); + function L1Sender__init( + address distribution_, + RewardTokenConfig calldata rewardTokenConfig_, + DepositTokenConfig calldata depositTokenConfig_ + ) external; /** - * The function to set the reward token's config. - * @param newConfig_ The new reward token's config. + * The function to get the deposit token's address. */ - function setRewardTokenConfig(RewardTokenConfig calldata newConfig_) external; + function unwrappedDepositToken() external view returns (address); /** - * The function to set the deposit token's config. - * @param newConfig_ The new deposit token's config. + * The function to send the message of mint of reward token to the L2. + * @param user_ The user's address to mint reward tokens. + * @param amount_ The amount of reward tokens to mint. + * @param refundTo_ The address to refund the overpaid gas. */ - function setDepositTokenConfig(DepositTokenConfig calldata newConfig_) external; + function sendMintMessage(address user_, uint256 amount_, address refundTo_) external payable; /** - * The function to send all current balance of the deposit token to the L2. - * @param gasLimit_ The gas limit for the L2 transaction. - * @param maxFeePerGas_ The max fee per gas for the L2 transaction. - * @param maxSubmissionCost_ The max submission cost for the L2 transaction. - * @return The unique identifier for withdrawal. + * The function to set the deposit token to the L2. + * @param gasLimit_ The gas limit for the transaction. + * @param maxFeePerGas_ The maximum fee per gas for the transaction. + * @param maxSubmissionCost_ The maximum submission cost for the transaction. */ function sendDepositToken( uint256 gasLimit_, uint256 maxFeePerGas_, uint256 maxSubmissionCost_ ) external payable returns (bytes memory); - - /** - * The function to send the message of mint of reward token to the L2. - * @param user_ The user's address to mint reward tokens. - * @param amount_ The amount of reward tokens to mint. - * @param refundTo_ The address to refund the overpaid gas. - */ - function sendMintMessage(address user_, uint256 amount_, address refundTo_) external payable; } diff --git a/contracts/interfaces/L2/IL2Factory.sol b/contracts/interfaces/L2/IL2Factory.sol new file mode 100644 index 0000000..df40cfd --- /dev/null +++ b/contracts/interfaces/L2/IL2Factory.sol @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {IL2TokenReceiver} from "./IL2TokenReceiver.sol"; + +interface IL2Factory { + /** + * The enum that represents the deployed L2 contract. + */ + enum PoolType { + L2_MESSAGE_RECEIVER, + L2_TOKEN_RECEIVER, + MOR20 + } + + /** + * The struct that represents the parameters for + * deploying specific L2 contracts. + * @param protocolName The protocol name. + * @param mor20Name The MOR20 name. + * @param mor20Symbol The MOR20 symbol. + * @param l1Sender The L1 sender address. + * @param firstSwapParams_ The first swap parameters. + * @param secondSwapFee The second swap fee. + */ + struct L2Params { + string protocolName; + string mor20Name; + string mor20Symbol; + address l1Sender; + IL2TokenReceiver.SwapParams firstSwapParams_; + uint24 secondSwapFee; + } + + /** + * The struct that represents the external dependencies for the Uniswap contract. + * @param router The router address. + * @param nonfungiblePositionManager The nonfungible position manager address. + */ + struct UniswapExternalDeps { + address router; + address nonfungiblePositionManager; + } + + /** + * The struct that represents the external dependencies for the LZ contract. + * @param endpoint The endpoint address. + * @param oftEndpoint The OFT endpoint address. + * @param senderChainId The sender chain ID. + */ + struct LzExternalDeps { + address endpoint; + address oftEndpoint; + uint16 senderChainId; + } + + /** + * The function that initializes the contract. + */ + function L2Factory_init() external; + + /** + * The function that sets the LZ external dependencies. + * @param lzExternalDeps_ The LZ external dependencies. + */ + function setLzExternalDeps(LzExternalDeps calldata lzExternalDeps_) external; + + /** + * The function that sets the Uniswap external dependencies. + * @param uniswapExternalDeps_ The Uniswap external dependencies. + */ + function setUniswapExternalDeps(UniswapExternalDeps calldata uniswapExternalDeps_) external; + + /** + * The function that deploys the L2 contracts. + * @param l2Params_ The L2 parameters. + */ + function deploy(L2Params calldata l2Params_) external; +} diff --git a/contracts/interfaces/IL2MessageReceiver.sol b/contracts/interfaces/L2/IL2MessageReceiver.sol similarity index 95% rename from contracts/interfaces/IL2MessageReceiver.sol rename to contracts/interfaces/L2/IL2MessageReceiver.sol index 7aaece2..c1cf139 100644 --- a/contracts/interfaces/IL2MessageReceiver.sol +++ b/contracts/interfaces/L2/IL2MessageReceiver.sol @@ -51,17 +51,17 @@ interface IL2MessageReceiver is ILayerZeroReceiver { } /** - * The function to get the reward token's address. - * @return The address of reward token. + * The function to initialize the contract. + * @param rewardToken_ The address of reward token. + * @param config_ The config data. */ - function rewardToken() external view returns (address); + function L2MessageReceiver__init(address rewardToken_, Config calldata config_) external; /** - * The function to set the params. - * @param rewardToken_ The address of reward token. - * @param config_ The config data. + * The function to get the reward token's address. + * @return The address of reward token. */ - function setParams(address rewardToken_, Config calldata config_) external; + function rewardToken() external view returns (address); /** * The function to call the nonblockingLzReceive. diff --git a/contracts/interfaces/IL2TokenReceiverV2.sol b/contracts/interfaces/L2/IL2TokenReceiver.sol similarity index 65% rename from contracts/interfaces/IL2TokenReceiverV2.sol rename to contracts/interfaces/L2/IL2TokenReceiver.sol index 268aeb4..f55a99d 100644 --- a/contracts/interfaces/IL2TokenReceiverV2.sol +++ b/contracts/interfaces/L2/IL2TokenReceiver.sol @@ -7,19 +7,17 @@ import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Recei /** * This is Swap contract that swaps tokens using Uniswap V3. */ -interface IL2TokenReceiverV2 is IERC165, IERC721Receiver { +interface IL2TokenReceiver is IERC165, IERC721Receiver { /** * The structure that stores the swap params. * @param tokenIn The address of the token to swap from. * @param tokenOut The address of the token to swap to. * @param fee The fee of the swap. - * @param sqrtPriceLimitX96 The price limit of the swap. */ struct SwapParams { address tokenIn; address tokenOut; uint24 fee; - uint160 sqrtPriceLimitX96; } /** @@ -56,6 +54,24 @@ interface IL2TokenReceiverV2 is IERC165, IERC721Receiver { uint256 amount1Min ); + /** + * The event that is emitted when the liquidity is decreased. + * @param tokenId The ID of the position. + * @param amount0 The amount of token0 received back. + * @param amount1 The amount of token1 received back. + * @param liquidity The amount of liquidity to receive back. + * @param amount0Min The minimum amount of token0 to receive back. + * @param amount1Min The minimum amount of token1 to receive back. + */ + event LiquidityDecreased( + uint256 indexed tokenId, + uint256 amount0, + uint256 amount1, + uint256 liquidity, + uint256 amount0Min, + uint256 amount1Min + ); + /** * The event that is emitted when the fees are collected. * @param tokenId The ID of the position. @@ -64,6 +80,20 @@ interface IL2TokenReceiverV2 is IERC165, IERC721Receiver { */ event FeesCollected(uint256 indexed tokenId, uint256 amount0, uint256 amount1); + /** + * The function to initialize the contract. + * @param router_ The address of the router. + * @param nonfungiblePositionManager_ The address of the nonfungible position manager. + * @param firstSwapParams_ The initial swap params. + * @param secondSwapParams_ The secondary swap params. + */ + function L2TokenReceiver__init( + address router_, + address nonfungiblePositionManager_, + SwapParams memory firstSwapParams_, + SwapParams memory secondSwapParams_ + ) external; + /** * The function to edit the swap params. * @param params_ The new swap params. @@ -76,14 +106,16 @@ interface IL2TokenReceiverV2 is IERC165, IERC721Receiver { * @param amountIn_ The amount of tokens to swap. * @param amountOutMinimum_ The minimum amount of tokens to receive. * @param deadline_ The deadline for the swap. - * @param isEditFirstParams_ The flag to indicate if the swapParams is initial. + * @param sqrtPriceLimitX96_ The price limit of the swap. + * @param isUseFirstSwapParams_ The flag to indicate if the swapParams is initial. * @return The amount of tokens received. */ function swap( uint256 amountIn_, uint256 amountOutMinimum_, uint256 deadline_, - bool isEditFirstParams_ + uint160 sqrtPriceLimitX96_, + bool isUseFirstSwapParams_ ) external returns (uint256); /** @@ -105,22 +137,33 @@ interface IL2TokenReceiverV2 is IERC165, IERC721Receiver { /** * The function to increase liquidity in the current price range. * @param tokenId The ID of the position. - * @param amountAdd0_ The amount of tokenIn to add. - * @param amountAdd1_ The amount of tokenOut to add. - * @param depositTokenAmountMin_ The minimum amount of deposit token to add. - * @param rewardTokenAmountMin_ The minimum amount of reward token to add. - * @return liquidity_ The amount of liquidity added. - * @return amount0_ The amount of token0 added. - * @return amount1_ The amount of token1 added. + * @param amount0Add_ The amount of token0 to add. + * @param amount1Add_ The amount of token1 to add. + * @param amount0Min_ The minimum amount of token0 to add. + * @param amount1Min_ The minimum amount of token1 to add. */ function increaseLiquidityCurrentRange( uint256 tokenId, - uint256 amountAdd0_, - uint256 amountAdd1_, - uint256 depositTokenAmountMin_, - uint256 rewardTokenAmountMin_ + uint256 amount0Add_, + uint256 amount1Add_, + uint256 amount0Min_, + uint256 amount1Min_ ) external returns (uint128 liquidity_, uint256 amount0_, uint256 amount1_); + /** + * The function to decrease liquidity in the current price range. + * @param tokenId_ The ID of the position. + * @param liquidity_ The amount of liquidity to receive back. + * @param amount0Min_ The minimum amount of token0 to receive back. + * @param amount1Min_ The minimum amount of token1 to receive back. + */ + function decreaseLiquidityCurrentRange( + uint256 tokenId_, + uint128 liquidity_, + uint256 amount0Min_, + uint256 amount1Min_ + ) external returns (uint256 amount0_, uint256 amount1_); + /** * The function to collect fees from the position. The fees are not transferred to the caller. * @param tokenId_ The ID of the position. diff --git a/contracts/interfaces/IMOROFT.sol b/contracts/interfaces/L2/IMOR20.sol similarity index 71% rename from contracts/interfaces/IMOROFT.sol rename to contracts/interfaces/L2/IMOR20.sol index 9d5a0b6..b34d640 100644 --- a/contracts/interfaces/IMOROFT.sol +++ b/contracts/interfaces/L2/IMOR20.sol @@ -3,17 +3,18 @@ pragma solidity ^0.8.20; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; -import {IOAppCore} from ".././@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/interfaces/IOAppCore.sol"; +import {IOAppCore} from "../../@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/interfaces/IOAppCore.sol"; /** - * This is the interface for MOROFT token contract. The token is ERC20 with burnable and Layer Zero OFT features. + * This is the interface for IMOR20 token contract. The token is ERC20 burnable and Layer Zero OFT features. */ -interface IMOROFT is IERC20, IERC165 { +interface IMOR20 is IERC20, IERC165 { /** - * The function to get the minter address. - * @return The minter address. + * The function to update `minter` addresses. + * @param minter_ The updated minter address. + * @param status_ The new status. True or false. */ - function minter() external view returns (address); + function updateMinter(address minter_, bool status_) external; /** * The function to mint tokens. diff --git a/contracts/interfaces/uniswap-v3/INonfungiblePositionManager.sol b/contracts/interfaces/uniswap-v3/INonfungiblePositionManager.sol index 79aabf2..9825386 100644 --- a/contracts/interfaces/uniswap-v3/INonfungiblePositionManager.sol +++ b/contracts/interfaces/uniswap-v3/INonfungiblePositionManager.sol @@ -13,6 +13,14 @@ interface INonfungiblePositionManager is IERC721 { uint256 deadline; } + struct DecreaseLiquidityParams { + uint256 tokenId; + uint128 liquidity; + uint256 amount0Min; + uint256 amount1Min; + uint256 deadline; + } + struct CollectParams { uint256 tokenId; address recipient; @@ -38,6 +46,10 @@ interface INonfungiblePositionManager is IERC721 { IncreaseLiquidityParams calldata params ) external payable returns (uint128 liquidity, uint256 amount0, uint256 amount1); + function decreaseLiquidity( + DecreaseLiquidityParams calldata params + ) external payable returns (uint256 amount0, uint256 amount1); + function collect(CollectParams calldata params) external payable returns (uint256 amount0, uint256 amount1); function positions( diff --git a/contracts/libs/MOR20Deployer.sol b/contracts/libs/MOR20Deployer.sol new file mode 100644 index 0000000..89b119a --- /dev/null +++ b/contracts/libs/MOR20Deployer.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {MOR20} from "../L2/MOR20.sol"; + +library MOR20Deployer { + event Mor20Deployed(address token); + + function deployMOR20( + string calldata mor20Name_, + string calldata mor20Symbol, + address oftEndpoint_, + address delegate_, + address l2MessageReceiver_ + ) external returns (address mor20) { + mor20 = address(new MOR20(mor20Name_, mor20Symbol, oftEndpoint_, delegate_, l2MessageReceiver_)); + + emit Mor20Deployed(mor20); + } +} diff --git a/contracts/mock/FactoryMock.sol b/contracts/mock/FactoryMock.sol new file mode 100644 index 0000000..65b3a99 --- /dev/null +++ b/contracts/mock/FactoryMock.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {Factory} from "../Factory.sol"; + +contract FactoryMock is Factory { + function Factory_init() external initializer { + __Pausable_init(); + __Ownable_init(); + __UUPSUpgradeable_init(); + __Factory_init(); + } + + function mockInit() external { + __Factory_init(); + } + + function deploy2(uint8 poolType_, string calldata poolName_) external returns (address) { + return _deploy2(poolType_, poolName_); + } +} diff --git a/contracts/mock/FactoryMockV2.sol b/contracts/mock/FactoryMockV2.sol new file mode 100644 index 0000000..e929f20 --- /dev/null +++ b/contracts/mock/FactoryMockV2.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {FactoryMock} from "./FactoryMock.sol"; + +contract FactoryMockV2 is FactoryMock { + function FactoryMockV2_init() external initializer { + __Pausable_init(); + __Ownable_init(); + __UUPSUpgradeable_init(); + __Factory_init(); + } + + function version() external pure returns (uint256) { + return 2; + } +} diff --git a/contracts/mock/FeeConfigV2.sol b/contracts/mock/FeeConfigV2.sol new file mode 100644 index 0000000..91dd2a5 --- /dev/null +++ b/contracts/mock/FeeConfigV2.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; + +contract FeeConfigV2 is UUPSUpgradeable { + function version() external pure returns (uint256) { + return 2; + } + + function _authorizeUpgrade(address) internal view override {} +} diff --git a/contracts/mock/L1FactoryV2.sol b/contracts/mock/L1FactoryV2.sol new file mode 100644 index 0000000..ec9e9cc --- /dev/null +++ b/contracts/mock/L1FactoryV2.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; + +contract L1FactoryV2 is UUPSUpgradeable { + function version() external pure returns (uint256) { + return 2; + } + + function _authorizeUpgrade(address) internal view override {} +} diff --git a/contracts/mock/L2FactoryV2.sol b/contracts/mock/L2FactoryV2.sol new file mode 100644 index 0000000..849f7d4 --- /dev/null +++ b/contracts/mock/L2FactoryV2.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; + +contract L2FactoryV2 is UUPSUpgradeable { + function version() external pure returns (uint256) { + return 2; + } + + function _authorizeUpgrade(address) internal view override {} +} diff --git a/contracts/mock/L2TokenRecevierV2.sol b/contracts/mock/L2TokenRecevierV2.sol new file mode 100644 index 0000000..3695d42 --- /dev/null +++ b/contracts/mock/L2TokenRecevierV2.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; + +contract L2TokenReceiverV2 is UUPSUpgradeable { + function version() external pure returns (uint256) { + return 2; + } + + function _authorizeUpgrade(address) internal view override {} +} diff --git a/contracts/mock/tokens/WETHMock.sol b/contracts/mock/tokens/WETHMock.sol new file mode 100644 index 0000000..76e9f8d --- /dev/null +++ b/contracts/mock/tokens/WETHMock.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract WETHMock is ERC20 { + constructor() ERC20("WETH", "WETH") {} + + function mint(address account_, uint256 amount_) external { + _mint(account_, amount_); + } +} diff --git a/deploy/1_L1.migration.ts b/deploy/1_L1.migration.ts new file mode 100644 index 0000000..1217393 --- /dev/null +++ b/deploy/1_L1.migration.ts @@ -0,0 +1,50 @@ +import { Deployer, Reporter } from '@solarity/hardhat-migrate'; + +import { parseConfig } from './helpers/config-parser'; + +import { + Distribution__factory, + ERC1967Proxy__factory, + FeeConfig__factory, + L1Factory__factory, + L1Sender__factory, +} from '@/generated-types/ethers'; +import { PoolTypesL1 } from '@/test/helpers/helper'; + +module.exports = async function (deployer: Deployer) { + const config = parseConfig(await deployer.getChainId()); + + const distributionImpl = await deployer.deploy(Distribution__factory); + const L1SenderImpl = await deployer.deploy(L1Sender__factory); + + const feeConfigImpl = await deployer.deploy(FeeConfig__factory); + const feeConfigProxy = await deployer.deploy(ERC1967Proxy__factory, [await feeConfigImpl.getAddress(), '0x'], { + name: 'FeeConfigProxy', + }); + + const feeConfig = await deployer.deployed(FeeConfig__factory, await feeConfigProxy.getAddress()); + + await feeConfig.__FeeConfig_init(config.feeConfig.treasury, config.feeConfig.baseFee); + + const l1FactoryImpl = await deployer.deploy(L1Factory__factory); + const l1FactoryProxy = await deployer.deploy(ERC1967Proxy__factory, [await l1FactoryImpl.getAddress(), '0x'], { + name: 'L1FactoryProxy', + }); + const l1Factory = await deployer.deployed(L1Factory__factory, await l1FactoryProxy.getAddress()); + + await l1Factory.L1Factory_init(); + + await l1Factory.setDepositTokenExternalDeps(config.depositTokenExternalDeps); + await l1Factory.setLzExternalDeps(config.lzExternalDeps); + await l1Factory.setArbExternalDeps(config.arbExternalDeps); + + await l1Factory.setImplementation(PoolTypesL1.DISTRIBUTION, distributionImpl); + await l1Factory.setImplementation(PoolTypesL1.L1_SENDER, L1SenderImpl); + + Reporter.reportContracts( + ['l1Factory', await l1Factory.getAddress()], + ['FeeConfig', await feeConfig.getAddress()], + ['DistributionImpl', await distributionImpl.getAddress()], + ['L1SenderImpl', await L1SenderImpl.getAddress()], + ); +}; diff --git a/deploy/1_L2.migration.ts b/deploy/1_L2.migration.ts new file mode 100644 index 0000000..29a16d0 --- /dev/null +++ b/deploy/1_L2.migration.ts @@ -0,0 +1,38 @@ +import { Deployer, Reporter } from '@solarity/hardhat-migrate'; + +import { parseConfig } from './helpers/config-parser'; + +import { + ERC1967Proxy__factory, + L2Factory__factory, + L2MessageReceiver__factory, + L2TokenReceiver__factory, +} from '@/generated-types/ethers'; +import { PoolTypesL2 } from '@/test/helpers/helper'; + +module.exports = async function (deployer: Deployer) { + const config = parseConfig(await deployer.getChainId()); + + const l2MessageReceiverImpl = await deployer.deploy(L2MessageReceiver__factory); + const l2TokenReceiverImpl = await deployer.deploy(L2TokenReceiver__factory); + + const l2FactoryImpl = await deployer.deploy(L2Factory__factory); + const l2FactoryProxy = await deployer.deploy(ERC1967Proxy__factory, [await l2FactoryImpl.getAddress(), '0x'], { + name: 'L2FactoryProxy', + }); + const l2Factory = await deployer.deployed(L2Factory__factory, await l2FactoryProxy.getAddress()); + + await l2Factory.L2Factory_init(); + + await l2Factory.setLzExternalDeps(config.lzTokenExternalDeps); + await l2Factory.setUniswapExternalDeps(config.uniswapExternalDeps); + + await l2Factory.setImplementation(PoolTypesL2.L2_MESSAGE_RECEIVER, l2MessageReceiverImpl); + await l2Factory.setImplementation(PoolTypesL2.L2_TOKEN_RECEIVER, l2TokenReceiverImpl); + + Reporter.reportContracts( + ['l2Factory', await l2Factory.getAddress()], + ['L2MessageReceiverImpl', await l2MessageReceiverImpl.getAddress()], + ['L2TokenReceiverImpl', await l2TokenReceiverImpl.getAddress()], + ); +}; diff --git a/deploy/1_bridge.migration.ts b/deploy/1_bridge.migration.ts deleted file mode 100644 index 9f2b116..0000000 --- a/deploy/1_bridge.migration.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { Deployer, Reporter, UserStorage } from '@solarity/hardhat-migrate'; - -import { parseConfig } from './helpers/config-parser'; - -import { - ERC1967Proxy__factory, - L2MessageReceiver__factory, - L2TokenReceiver__factory, - MOR__factory, -} from '@/generated-types/ethers'; -import { IL2TokenReceiver } from '@/generated-types/ethers/contracts/L2TokenReceiver'; - -module.exports = async function (deployer: Deployer) { - const config = parseConfig(await deployer.getChainId()); - - let WStETH: string; - let swapRouter: string; - let nonfungiblePositionManager: string; - - if (config.L2) { - WStETH = config.L2.wStEth; - swapRouter = config.L2.swapRouter; - nonfungiblePositionManager = config.L2.nonfungiblePositionManager; - } else { - // deploy mock - // const stETHMock = await deployer.deploy(StETHMock__factory, [], { name: 'StETH on L2' }); - // const stETH = await stETHMock.getAddress(); - - // const wStEthMock = await deployer.deploy(WStETHMock__factory, [stETH], { name: 'Wrapped stETH on L2' }); - // WStETH = await wStEthMock.getAddress(); - - // const swapRouterMock = await deployer.deploy(SwapRouterMock__factory); - // swapRouter = await swapRouterMock.getAddress(); - - // const nonfungiblePositionManagerMock = await deployer.deploy(NonfungiblePositionManagerMock__factory); - // nonfungiblePositionManager = await nonfungiblePositionManagerMock.getAddress(); - return; - } - - const MOR = await deployer.deploy(MOR__factory, [config.cap]); - UserStorage.set('MOR', await MOR.getAddress()); - - const swapParams: IL2TokenReceiver.SwapParamsStruct = { - tokenIn: WStETH, - tokenOut: MOR, - fee: config.swapParams.fee, - sqrtPriceLimitX96: config.swapParams.sqrtPriceLimitX96, - }; - - const l2TokenReceiverImpl = await deployer.deploy(L2TokenReceiver__factory); - const l2TokenReceiverProxy = await deployer.deploy(ERC1967Proxy__factory, [l2TokenReceiverImpl, '0x'], { - name: 'L2TokenReceiver Proxy', - }); - UserStorage.set('L2TokenReceiver Proxy', await l2TokenReceiverProxy.getAddress()); - const l2TokenReceiver = L2TokenReceiver__factory.connect( - await l2TokenReceiverProxy.getAddress(), - await deployer.getSigner(), - ); - await l2TokenReceiver.L2TokenReceiver__init(swapRouter, nonfungiblePositionManager, swapParams); - - const l2MessageReceiverImpl = await deployer.deploy(L2MessageReceiver__factory); - const l2MessageReceiverProxy = await deployer.deploy(ERC1967Proxy__factory, [l2MessageReceiverImpl, '0x'], { - name: 'L2MessageReceiver Proxy', - }); - UserStorage.set('L2MessageReceiver Proxy', await l2MessageReceiverProxy.getAddress()); - const l2MessageReceiver = L2MessageReceiver__factory.connect( - await l2MessageReceiverProxy.getAddress(), - await deployer.getSigner(), - ); - await l2MessageReceiver.L2MessageReceiver__init(); - - await MOR.transferOwnership(l2MessageReceiver); - - Reporter.reportContracts( - ['L2TokenReceiver', await l2TokenReceiver.getAddress()], - ['L2MessageReceiver', await l2MessageReceiver.getAddress()], - ['MOR', await MOR.getAddress()], - ); -}; diff --git a/deploy/2_token.migration.ts b/deploy/2_token.migration.ts deleted file mode 100644 index f379eb9..0000000 --- a/deploy/2_token.migration.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { Deployer, Reporter, UserStorage } from '@solarity/hardhat-migrate'; - -import { parseConfig } from './helpers/config-parser'; - -import { Distribution__factory, ERC1967Proxy__factory, L1Sender__factory } from '@/generated-types/ethers'; -import { IL1Sender } from '@/generated-types/ethers/contracts/L1Sender'; -import { ZERO_ADDR } from '@/scripts/utils/constants'; - -module.exports = async function (deployer: Deployer) { - const config = parseConfig(await deployer.getChainId()); - - let stETH: string; - let wStEth: string; - - if (config.L1) { - stETH = config.L1.stEth; - wStEth = config.L1.wStEth; - } else { - // deploy mock - // const stETHMock = await deployer.deploy(StETHMock__factory, { name: 'StETH on L1' }); - // stETH = await stETHMock.getAddress(); - - // const wStEthMock = await deployer.deploy(WStETHMock__factory, [stETH], { name: 'wStETH on L1' }); - // wStEth = await wStEthMock.getAddress(); - return; - } - - let lzEndpointL1: string; - if (config.lzConfig) { - lzEndpointL1 = config.lzConfig.lzEndpointL1; - } else { - // deploy mock - // const LzEndpointL1Mock = await deployer.deploy(LZEndpointMock__factory, [config.chainsConfig.senderChainId], { - // name: 'LZEndpoint on L1', - // }); - // lzEndpointL1 = await LzEndpointL1Mock.getAddress(); - return; - } - - let arbitrumBridgeGatewayRouter: string; - if (config.arbitrumConfig) { - arbitrumBridgeGatewayRouter = config.arbitrumConfig.arbitrumBridgeGatewayRouter; - } else { - return; - } - - const distributionImpl = await deployer.deploy(Distribution__factory); - const distributionProxy = await deployer.deploy(ERC1967Proxy__factory, [distributionImpl, '0x'], { - name: 'Distribution Proxy', - }); - const distribution = Distribution__factory.connect(await distributionProxy.getAddress(), await deployer.getSigner()); - - const rewardTokenConfig: IL1Sender.RewardTokenConfigStruct = { - gateway: lzEndpointL1, - // receiver: '0xd4a8ECcBe696295e68572A98b1aA70Aa9277d427', - receiver: UserStorage.get('L2MessageReceiver Proxy'), - receiverChainId: config.chainsConfig.receiverChainId, - zroPaymentAddress: ZERO_ADDR, - adapterParams: '0x', - }; - const depositTokenConfig: IL1Sender.DepositTokenConfigStruct = { - token: wStEth, - gateway: arbitrumBridgeGatewayRouter, - // receiver: '0x47176B2Af9885dC6C4575d4eFd63895f7Aaa4790', - receiver: UserStorage.get('L2TokenReceiver Proxy'), - }; - - const l1SenderImpl = await deployer.deploy(L1Sender__factory); - const l1SenderProxy = await deployer.deploy(ERC1967Proxy__factory, [l1SenderImpl, '0x'], { - name: 'L1Sender Proxy', - }); - UserStorage.set('L1Sender Proxy', await l1SenderProxy.getAddress()); - const l1Sender = L1Sender__factory.connect(await l1SenderProxy.getAddress(), await deployer.getSigner()); - await l1Sender.L1Sender__init(distribution, rewardTokenConfig, depositTokenConfig); - - await distribution.Distribution_init(stETH, l1Sender, config.pools || []); - - if (config.pools) { - for (let i = 0; i < config.pools.length; i++) { - const pool = config.pools[i]; - - if (pool.whitelistedUsers && pool.whitelistedUsers.length > 0) { - const amounts = pool.amounts!; - await distribution.manageUsersInPrivatePool(i, pool.whitelistedUsers, amounts); - } - } - } - - Reporter.reportContracts( - ['Distribution', await distribution.getAddress()], - ['L1Sender', await l1Sender.getAddress()], - ); -}; diff --git a/deploy/3_init_bridge.migration.ts b/deploy/3_init_bridge.migration.ts deleted file mode 100644 index e053ebd..0000000 --- a/deploy/3_init_bridge.migration.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { Deployer, Reporter, UserStorage } from '@solarity/hardhat-migrate'; - -import { parseConfig } from './helpers/config-parser'; - -import { L1Sender__factory, L2MessageReceiver__factory, MOR__factory } from '@/generated-types/ethers'; -import { IL2MessageReceiver } from '@/generated-types/ethers/contracts/L2MessageReceiver'; - -module.exports = async function (deployer: Deployer) { - const config = parseConfig(await deployer.getChainId()); - - let lzEndpointL2: string; - if (config.lzConfig) { - lzEndpointL2 = config.lzConfig.lzEndpointL2; - } else { - // deploy mock - // const lzEndpointL2Mock = await deployer.deploy(LZEndpointMock__factory, [config.chainsConfig.receiverChainId], { - // name: 'LZEndpoint on L2', - // }); - // lzEndpointL2 = await lzEndpointL2Mock.getAddress(); - return; - } - - const l2MessageReceiver = L2MessageReceiver__factory.connect( - // '0xd4a8ECcBe696295e68572A98b1aA70Aa9277d427', - UserStorage.get('L2MessageReceiver Proxy'), - await deployer.getSigner(), - ); - - // const l1Sender = L1Sender__factory.connect('0x2Efd4430489e1a05A89c2f51811aC661B7E5FF84', await deployer.getSigner()); - - // const mor = MOR__factory.connect('0x7431aDa8a591C955a994a21710752EF9b882b8e3', await deployer.getSigner()); - - const l1Sender = L1Sender__factory.connect(UserStorage.get('L1Sender Proxy'), await deployer.getSigner()); - - const mor = MOR__factory.connect(UserStorage.get('MOR'), await deployer.getSigner()); - - const l2MessageReceiverConfig: IL2MessageReceiver.ConfigStruct = { - gateway: lzEndpointL2, - sender: l1Sender, - senderChainId: config.chainsConfig.senderChainId, - }; - - const tx = await l2MessageReceiver.setParams(mor, l2MessageReceiverConfig); - - await Reporter.reportTransactionByHash(tx.hash); -}; diff --git a/deploy/4_moroft_testnet.migration.ts b/deploy/4_moroft_testnet.migration.ts deleted file mode 100644 index 787578b..0000000 --- a/deploy/4_moroft_testnet.migration.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Deployer, Reporter } from '@solarity/hardhat-migrate'; - -import { MOROFT__factory } from '@/generated-types/ethers'; - -const lzEndpointV2 = '0x6edce65403992e310a62460808c4b910d972f10f'; - -module.exports = async function (deployer: Deployer) { - const deployerAddress = await (await deployer.getSigner()).getAddress(); - - const mor = await deployer.deploy(MOROFT__factory, [lzEndpointV2, deployerAddress, deployerAddress]); - - Reporter.reportContracts(['MOROFT', await mor.getAddress()]); -}; - -// npx hardhat migrate --only 4 -// npx hardhat migrate --network arbitrum_sepolia --only 4 --verify -// npx hardhat migrate --network sepolia --only 4 --verify -// npx hardhat migrate --network mumbai --only 4 --verify diff --git a/deploy/data/config.json b/deploy/data/config.json index b521519..7c210b8 100644 --- a/deploy/data/config.json +++ b/deploy/data/config.json @@ -1,214 +1,28 @@ { - "cap": "42000000000000000000000000", - "chainsConfig": { - "senderChainId": 101, - "receiverChainId": 110 + "feeConfig": { + "baseFee": "1000000000000000000000000", + "treasury": "0x901F2d23823730fb7F2356920e0E273EFdCdFe17" }, - "pools": [ - { - "payoutStart": 1707393600, - "decreaseInterval": 86400, - "withdrawLockPeriod": 604800, - "claimLockPeriod": 7776000, - "withdrawLockPeriodAfterStake": 604800, - "initialReward": "3456000000000000000000", - "rewardDecrease": "592558728240000000", - "minimalStake": "10000000000000000", - "isPublic": true - }, - { - "payoutStart": 1707393600, - "decreaseInterval": 86400, - "withdrawLockPeriod": 0, - "claimLockPeriod": 7776000, - "withdrawLockPeriodAfterStake": 0, - "initialReward": "3456000000000000000000", - "rewardDecrease": "592558728240000000", - "minimalStake": "0", - "isPublic": false, - "whitelistedUsers": [ - "0x7aE68C76771131835e90AEfF44f1c03d07503B92", - "0xe27458C8Bb3D9Bf298BC8FE47F0Cfab40bc45963", - "0x98eFf980C57c9D333340b3856481bF7B8698987c", - "0xb75DB2216E7047C7b6bebE1057CF7c99e5D87eD0", - "0x01974549C9B9a30d47c548A16b120b1cAa7B586C", - "0xC16C04dE65E2EBA60B78294E23953f5EB17F9202", - "0x2ed6D479C15272438c38c7c47994e3e8df3138c8", - "0x48d0EAc727A7e478f792F16527012452a000f2bd", - "0xd8737632121D4C50b76FA3349B3C197c4574170a", - "0x8388298D1B9601CE2da78127605570b4878f2cFa", - "0x8d00121e79cBb741A7da7B0598762B8dE3394E62", - "0x683C0173BD2214E95cA9FFB1B437c12A9697ABe6", - "0xf3ed92789Ec71174FCDff6aA97239E5456a1b5c0", - "0x46266F255E49756847587eabDDFc4D336Ef268d2", - "0x704a10D661A0ce0be6C3379d93FC47f13be4ab50", - "0x05A1ff0a32bc24265BCB39499d0c5D9A6cb2011c", - "0xaBd9faA94384595ea304ddce2Ac8e097970eD5b3", - "0x63d45Be2F2828c0f8e2b4Cff745e89ae74A6d034", - "0x5EBe5223831523823528Ef0a7EdF67D288B1B070", - "0x931A833E7A699163907bfE8E36cbFbF2E6fa5fd1", - "0xe4D28FE30829A825B7379EF28d5d91174436C899", - "0x62aF7c48Cf412162465A8CaFdE44dFb17bA96038", - "0xED48BAcB38Aa79F0C44133baF73a74F44cEdD5F9", - "0x6c4eB02ac1060Cf486153c0c02829739E45409F3", - "0xA17b82286601825a5ac45Dcb01f38242D1a79548", - "0x0A094e2Cb26578E688a28e7040199ADC6cd490Da", - "0x5Ae426D938fDe32Ac9268d253F1Fe3Bb6AB9d5Dd", - "0xE9e3c537E405b1F40A3e8a8EcDC4dFEA720a6Bd0", - "0x26180446C2f754256ff383b4a2558a6774551295", - "0x905591EDa020c98E13fB3b52BCcB9277dD559FE8", - "0x6F3473a6B1d89a325Bf33D592ba3E4c2b90E4014", - "0x1461F599ecCeC516cf4704253Ab26dF69003439E", - "0xbA3777CF0832922F35A1a414875E3247e2608E3C", - "0x4e3CDA01A86922586326b84E6D156097AAA94043", - "0x42FeeC5c7e7D3c725864A2716CA357Fa9993CCC0", - "0x8979CdD44b96007753e4BBc429De6f7989DAD16C", - "0x4c496ea116A09651778e480B2e7E5aBA78514521", - "0xa4b427AF1952374b3527CeBcfDCC9D063DFe802c", - "0xDeb55cA4D98218aEC15764651eeEF5799356D63E", - "0x12E6F8DfdBb81F6285f0dF132165D46F20FC85e3", - "0xcCc442eDa69e466e1C54e3A7258AE8Aab508aefC", - "0x26798cB968c8aBC7155BBfC8AcB4485A9f121666", - "0xbE97d54Cfd1ff86E21F2aEB02AA5219Bc00486E0", - "0x7dDeBE7D0A896f4BBfeDB68b892c35BDF6F21d8e", - "0x8419aF9a5d31797368523aa568dAf064ba067dD1", - "0x0A1B78004Ab0254967855f6c60230f350757043a", - "0xa7e32D41f5b94352F4De412Ff6E5fA91ed7532A2", - "0xe4C6B02b6095A18D4FF867218A31ff7b11C3e828", - "0x6357B6C6E2d79b570a0C5A0b17e20Ab46f72BCf8", - "0x9cd4cde2D492f79FfFC62634ebd22994681743be", - "0xb3fDCCE55C5452F9E9F9dd7713a4B28dAae90bc3", - "0x70305F11944ba4622ce0AE2e4D9D8023f54CeA85", - "0xe36B28c72C1F7f8a4D87cA5d64B63233875173a4", - "0xD9A157cCF1415970020d15a8B45A0212ABb9c074", - "0x478aC52c212d5e98EdF0e5877f50AfF38f1f647E", - "0xCe1Aa48E8e502D1444C54603D96E79ce273c1933", - "0x852B214aa1eB0C10Bbd900d4390efe03ea95152A", - "0x4690158f5e6086017eed0c2854a0fb4571ed4f0e", - "0x4d2D9F00B82cC646bc388b5e8B33A82d4E5ffA0A", - "0x3B04546eC9f04f375B40d3b753894De5cC16e41b", - "0xb18Eb611f4C7d51420462c926Ae2Cc0b5d362e7A", - "0xFE55121BD7dcFc20258AFFA7bad42Bba1585d808", - "0xd5FB8fccF9F35B1bb881ccee141EC5243F814B98", - "0xEe8547dEbc268863d2e22c63f2d705b4Fe08ae5a", - "0x270c7F012566b850Afd00b2B7B4be743CF7Cdb95", - "0x1be920ef263005d1ade89ee090782e80a2ec7a4f", - "0x42A832e64270948700B92C8Ecc1c977eEe82af41", - "0x07e319ccc91028981a9d2fC1F682251Dfd20dA8b", - "0x552DfAaFBD72747DcD56DfDa79663BD23292E0b7", - "0xA28F9fe27F2E2a39ca0DB31381234f35e420e98E", - "0x20c08e787835c2F0BC7F273EF22A46FF2fd69cB5", - "0x1723a88158118AD1AE03A873B2dF1e8DE4FB921e", - "0xBb46dEF4367111fCa4cdcCfb34CE48DFABbe5e51", - "0x373A2b98B1FD70C62EFc7f3b27272eCDBDfD856F", - "0x1823e8af312D8d14CdCEDa785BF721a5438D0Cd3", - "0x0e50f6bc9691BB5c56eA4775322b177b655805c0", - "0x7289c33b3640E9928256c2ce237452Cb3C30D84a", - "0xD8A5529690cDf546FDcF07D593947cE298d60C51", - "0x81e94bB131b184Dcb2CEdC4Ac95622058176290A", - "0xc6A4CE513dD749FB25C35E044F00646abD8F8969", - "0x38c7e9E4917B521E5e13CEaB4Ff39448Cc85d7f0", - "0x3444085881C71ec2FB9696946872767FC321BE9D", - "0xf600a01dAd6c73dB4886b136B71ac39cE32B0244", - "0x236eC68fbB8D07a9Da4f025191Ac87458A9d00C1", - "0x0d449d300AfC0C1D488C79CB9E34ecC3042761ad", - "0xbdDE00C6545B0C2d658961441d0Fe83d894841a7", - "0x2440C6B5dBE0093E71c8563D399521d056460985", - "0xCC5e733cB2E7a59EFC5463575EfC3584D5922486", - "0x5df7eb97138Eb47c7674F939571465c5f02f636A", - "0xc2A09707c32C5C37aA069Dd62D23f1F8F8d0e525", - "0x5c134E4f25c1c592377F7e37eC0cF399B5D05753", - "0x20272088DaBf3d35D60331bfb359635d0Dd1f10B", - "0x5E2f339eaDf2b2E66043afc5DbAca3266FaD5860", - "0xAcC5E71Bcb4a79439D3b5B1AEBa011Ea82270301", - "0x972ca578545356A17646cB4D9BF50F469497E4C2", - "0x6C6bb551C4cBbC7B530a6253Fe0B9CeB0ea77f9A", - "0xe93075Bd6B16a681F9959BAe42C050Cd36600F9B", - "0x3a92f10694f38f2bea6A3794c3bD06880572Dc1f", - "0x0Ba7a85Af6323686a1fE29423bd72c2ce81Fe923", - "0x08f342A971aBe964eb73701b2FF5422b5FDAF5C0", - "0xeB25C57E67BDe01f58ca5056e8A3aE16B2bDB8B7", - "0x400e2c3097Aa82c0Ac8f8F2Cc4FaEe3A10fF382c", - "0x9e762109CD97f8CAD5323e6d6E3b15640Aa4B778", - "0x411A4D3B06ad7cEDd6dbC423112fe5586e096e56", - "0x9d28ABd25Ce3b65E8F1EC9818a230acEB604Bc82", - "0xe83A996F597f5EFAe542F293A2B3dbDdab5E75C3", - "0xc6E0D2162eA3a8a040455Bd471e925F184f1b4da", - "0x5694baAEaCa2C419306c9Bf5dbfdAC7F92c7704c", - "0x2969a7A704354E56124Cc18c9CC4847B0Dc7EB16", - "0x1002155825193612e972920338cF7C7bFcC15625", - "0xEbE2a63d8C69B16E58F75B6f221b8ea16745383e", - "0x07d4b4d9a2cc58085651dc66dca774b2f38b979e", - "0xfe17147301888fBEEc36be3a8e6E4f883496ed33" - ], - "amounts": [ - 41, 22, 50, 100, 32, 120, 280, 164, 80, 25, 20, 226, 80, 20, 10, 30, 22, - 16, 18, 275, 264, 165, 10, 10, 25, 10, 130, 52, 21, 20, 20, 25, 100, 11, - 12, 11, 12, 140, 190, 210, 240, 9, 470, 346, 10, 620, 483, 20, 200, 120, - 100, 310, 50, 25, 25, 20, 15, 10, 10, 10, 10, 10, 100, 10, 30, 50, 10, - 50, 50, 100, 20, 10, 10, 120, 120, 120, 120, 640, 20, 10, 390, 677, 410, - 319, 399, 398, 380, 860, 100, 100, 100, 100, 100, 100, 100, 100, 206, - 207, 40, 12, 32, 2, 410, 1, 10, 9, 740, 40, 10, 610, 1840, 80, 2582 - ] - }, - { - "payoutStart": 1707393600, - "decreaseInterval": 86400, - "withdrawLockPeriod": 0, - "claimLockPeriod": 7776000, - "withdrawLockPeriodAfterStake": 0, - "initialReward": "3456000000000000000000", - "rewardDecrease": "592558728240000000", - "minimalStake": "0", - "isPublic": false, - "whitelistedUsers": ["0x1FE04BC15Cf2c5A2d41a0b3a96725596676eBa1E"], - "amounts": ["1"] - }, - { - "payoutStart": 1707393600, - "decreaseInterval": 86400, - "withdrawLockPeriod": 0, - "claimLockPeriod": 7776000, - "withdrawLockPeriodAfterStake": 0, - "initialReward": "3456000000000000000000", - "rewardDecrease": "592558728240000000", - "minimalStake": "0", - "isPublic": false, - "whitelistedUsers": ["0x1FE04BC15Cf2c5A2d41a0b3a96725596676eBa1E"], - "amounts": ["1"] - }, - { - "payoutStart": 1707393600, - "decreaseInterval": 86400, - "withdrawLockPeriod": 0, - "claimLockPeriod": 7603200, - "withdrawLockPeriodAfterStake": 0, - "initialReward": "576000000000000000000", - "rewardDecrease": "98759788040000000", - "minimalStake": "0", - "isPublic": false, - "whitelistedUsers": ["0x1FE04BC15Cf2c5A2d41a0b3a96725596676eBa1E"], - "amounts": ["1"] - } - ], - "L1": { - "stEth": "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84", - "wStEth": "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0" + "depositTokenExternalDeps": { + "token": "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84", + "wToken": "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0" }, - "L2": { - "swapRouter": "0xE592427A0AEce92De3Edee1F18E0157C05861564", - "nonfungiblePositionManager": "0xC36442b4a4522E871399CD717aBDD847Ab11FE88", - "wStEth": "0x5979D7b546E38E414F7E9822514be443A4800529" + "lzExternalDeps": { + "endpoint": "0x66A71Dcef29A0fFBDBE3c6a460a3B5BC225Cd675", + "zroPaymentAddress": "0x0000000000000000000000000000000000000000", + "adapterParams": "0x", + "destinationChainId": 110 }, - "swapParams": { - "fee": 3000, - "sqrtPriceLimitX96": 0 + "arbExternalDeps": { + "endpoint": "0x72Ce9c846789fdB6fC1f34aC4AD25Dd9ef7031ef" }, - "arbitrumConfig": { - "arbitrumBridgeGatewayRouter": "0x72Ce9c846789fdB6fC1f34aC4AD25Dd9ef7031ef" + "lzTokenExternalDeps": { + "endpoint": "0x3c2269811836af69497E5F486A85D7316753cf62", + "oftEndpoint": "0x1a44076050125825900e736c501f859c50fE728c", + "senderChainId": 101 }, - "lzConfig": { - "lzEndpointL1": "0x66A71Dcef29A0fFBDBE3c6a460a3B5BC225Cd675", - "lzEndpointL2": "0x3c2269811836af69497E5F486A85D7316753cf62" + "uniswapExternalDeps": { + "router": "0xE592427A0AEce92De3Edee1F18E0157C05861564", + "nonfungiblePositionManager": "0xC36442b4a4522E871399CD717aBDD847Ab11FE88" } } diff --git a/deploy/data/config_goerli.json b/deploy/data/config_goerli.json deleted file mode 100644 index 9891da6..0000000 --- a/deploy/data/config_goerli.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "cap": "1000000000000000000000000", - "chainsConfig": { - "senderChainId": 10121, - "receiverChainId": 10143 - }, - "pools": [ - { - "payoutStart": 1735925178, - "decreaseInterval": 86400, - "withdrawLockPeriod": 120, - "claimLockPeriod": 60, - "withdrawLockPeriodAfterStake": 30, - "initialReward": "14400000000000000000000", - "rewardDecrease": "2468994701000000000", - "minimalStake": "1000000000000000", - "isPublic": true - }, - { - "payoutStart": 1735925178, - "decreaseInterval": 60, - "withdrawLockPeriod": 1, - "claimLockPeriod": 1, - "withdrawLockPeriodAfterStake": 30, - "initialReward": "100000000000000000000", - "rewardDecrease": "100000000000000000000", - "minimalStake": "1000000000000000", - "isPublic": false, - "whitelistedUsers": [ - "0x901F2d23823730fb7F2356920e0E273EFdCdFe17", - "0x19ec1E4b714990620edf41fE28e9a1552953a7F4" - ], - "amounts": ["1", "1"] - } - ], - "L1": { - "stEth": "0x1643E812aE58766192Cf7D2Cf9567dF2C37e9B7F", - "wStEth": "0x6320cD32aA674d2898A68ec82e869385Fc5f7E2f" - }, - "L2": { - "swapRouter": "0xE592427A0AEce92De3Edee1F18E0157C05861564", - "nonfungiblePositionManager": "0xC36442b4a4522E871399CD717aBDD847Ab11FE88", - "wStEth": "0xbED18985eC648Ce4b0C5Fc3061d1323116702BC4" - }, - "swapParams": { - "fee": 10000, - "sqrtPriceLimitX96": 0 - }, - "arbitrumConfig": { - "arbitrumBridgeGatewayRouter": "0x0ecCFbBEe34f04187361818832385EB4cC11b678" - }, - "lzConfig": { - "lzEndpointL1": "0xbfD2135BFfbb0B5378b56643c2Df8a87552Bfa23", - "lzEndpointL2": "0x6aB5Ae6822647046626e83ee6dB8187151E1d5ab" - } -} diff --git a/deploy/data/config_localhost.json b/deploy/data/config_localhost.json index 4c965c2..7c210b8 100644 --- a/deploy/data/config_localhost.json +++ b/deploy/data/config_localhost.json @@ -1,40 +1,28 @@ { - "cap": "1000000000000000000000000", - "chainsConfig": { - "senderChainId": 10121, - "receiverChainId": 10143 + "feeConfig": { + "baseFee": "1000000000000000000000000", + "treasury": "0x901F2d23823730fb7F2356920e0E273EFdCdFe17" }, - "pools": [ - { - "payoutStart": 1765790236, - "decreaseInterval": 86400, - "withdrawLockPeriod": 120, - "claimLockPeriod": 60, - "withdrawLockPeriodAfterStake": 30, - "initialReward": "14400000000000000000000", - "rewardDecrease": "2468994701000000000", - "minimalStake": "1000000000000000", - "isPublic": true - }, - { - "payoutStart": 1765790236, - "decreaseInterval": 60, - "withdrawLockPeriod": 1, - "claimLockPeriod": 1, - "withdrawLockPeriodAfterStake": 30, - "initialReward": "100000000000000000000", - "rewardDecrease": "100000000000000000000", - "minimalStake": "1000000000000000", - "isPublic": false, - "whitelistedUsers": [ - "0x901F2d23823730fb7F2356920e0E273EFdCdFe17", - "0x19ec1E4b714990620edf41fE28e9a1552953a7F4" - ], - "amounts": ["1", "1"] - } - ], - "swapParams": { - "fee": 10000, - "sqrtPriceLimitX96": 0 + "depositTokenExternalDeps": { + "token": "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84", + "wToken": "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0" + }, + "lzExternalDeps": { + "endpoint": "0x66A71Dcef29A0fFBDBE3c6a460a3B5BC225Cd675", + "zroPaymentAddress": "0x0000000000000000000000000000000000000000", + "adapterParams": "0x", + "destinationChainId": 110 + }, + "arbExternalDeps": { + "endpoint": "0x72Ce9c846789fdB6fC1f34aC4AD25Dd9ef7031ef" + }, + "lzTokenExternalDeps": { + "endpoint": "0x3c2269811836af69497E5F486A85D7316753cf62", + "oftEndpoint": "0x1a44076050125825900e736c501f859c50fE728c", + "senderChainId": 101 + }, + "uniswapExternalDeps": { + "router": "0xE592427A0AEce92De3Edee1F18E0157C05861564", + "nonfungiblePositionManager": "0xC36442b4a4522E871399CD717aBDD847Ab11FE88" } } diff --git a/deploy/data/config_sepolia.json b/deploy/data/config_sepolia.json index e07087a..2213c06 100644 --- a/deploy/data/config_sepolia.json +++ b/deploy/data/config_sepolia.json @@ -1,56 +1,28 @@ { - "cap": "1000000000000000000000000", - "chainsConfig": { - "senderChainId": 10161, - "receiverChainId": 10231 + "feeConfig": { + "baseFee": "1000000000000000000000000", + "treasury": "0x901F2d23823730fb7F2356920e0E273EFdCdFe17" }, - "pools": [ - { - "payoutStart": 1704360948, - "decreaseInterval": 86400, - "withdrawLockPeriod": 120, - "claimLockPeriod": 60, - "withdrawLockPeriodAfterStake": 30, - "initialReward": "14400000000000000000000", - "rewardDecrease": "2468994701000000000", - "minimalStake": "10000000000", - "isPublic": true - }, - { - "payoutStart": 1704360948, - "decreaseInterval": 60, - "withdrawLockPeriod": 1, - "claimLockPeriod": 1, - "withdrawLockPeriodAfterStake": 30, - "initialReward": "100000000000000000000", - "rewardDecrease": "100000000000000000000", - "minimalStake": "10000000000", - "isPublic": false, - "whitelistedUsers": [ - "0x901F2d23823730fb7F2356920e0E273EFdCdFe17", - "0x19ec1E4b714990620edf41fE28e9a1552953a7F4" - ], - "amounts": ["1", "1"] - } - ], - "L1": { - "stEth": "0xe6D01D086a844a61641C75f1BCA572e7aa70e154", - "wStEth": "0xE7d86F503F3dA0DEF5660420F667BF873d3D6A8f" + "depositTokenExternalDeps": { + "token": "0xe6D01D086a844a61641C75f1BCA572e7aa70e154", + "wToken": "0xE7d86F503F3dA0DEF5660420F667BF873d3D6A8f" }, - "L2": { - "swapRouter": "0xE592427A0AEce92De3Edee1F18E0157C05861564", - "nonfungiblePositionManager": "0xC36442b4a4522E871399CD717aBDD847Ab11FE88", - "wStEth": "0xfD5d8Dc9918e04673b9F5F332cE63BcC3BE427a2" + "lzExternalDeps": { + "endpoint": "0xae92d5aD7583AD66E49A0c67BAd18F6ba52dDDc1", + "zroPaymentAddress": "0x0000000000000000000000000000000000000000", + "adapterParams": "0x", + "destinationChainId": 10231 }, - "swapParams": { - "fee": 10000, - "sqrtPriceLimitX96": 0 + "arbExternalDeps": { + "endpoint": "0xcE18836b233C83325Cc8848CA4487e94C6288264" }, - "arbitrumConfig": { - "arbitrumBridgeGatewayRouter": "0xcE18836b233C83325Cc8848CA4487e94C6288264" + "lzTokenExternalDeps": { + "endpoint": "0x6098e96a28E02f27B1e6BD381f870F1C8Bd169d3", + "oftEndpoint": "0x6EDCE65403992e310A62460808c4b910D972f10f", + "senderChainId": 10161 }, - "lzConfig": { - "lzEndpointL1": "0xae92d5aD7583AD66E49A0c67BAd18F6ba52dDDc1", - "lzEndpointL2": "0x6098e96a28E02f27B1e6BD381f870F1C8Bd169d3" + "uniswapExternalDeps": { + "router": "0xE592427A0AEce92De3Edee1F18E0157C05861564", + "nonfungiblePositionManager": "0xC36442b4a4522E871399CD717aBDD847Ab11FE88" } } diff --git a/deploy/deploy-all.sh b/deploy/deploy-all.sh deleted file mode 100755 index 15372bc..0000000 --- a/deploy/deploy-all.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash -set -e - -verifyL1=$([ '$1' = 'localhost' ] && echo --verify || echo '') -verifyL2=$([ '$2' = 'localhost' ] && echo --verify || echo '') - -npx hardhat migrate --network $2 --only 1 $verifyL2 $3 -npx hardhat migrate --network $1 --only 2 $verifyL1 --continue -npx hardhat migrate --network $2 --only 3 $verifyL2 --continue diff --git a/deploy/helpers/config-parser.ts b/deploy/helpers/config-parser.ts index a12adfc..bf16007 100644 --- a/deploy/helpers/config-parser.ts +++ b/deploy/helpers/config-parser.ts @@ -1,41 +1,24 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import { BigNumberish } from 'ethers'; import { readFileSync } from 'fs'; -import { IDistribution } from '@/generated-types/ethers'; +import { IL1Factory, IL2Factory } from '@/generated-types/ethers'; import { ZERO_ADDR } from '@/scripts/utils/constants'; -export type Config = { - cap: number; - chainsConfig: { - senderChainId: number; - receiverChainId: number; - }; - pools?: PoolInitInfo[]; - L1?: { - stEth: string; - wStEth: string; - }; - L2?: { - swapRouter: string; - nonfungiblePositionManager: string; - wStEth: string; - }; - swapParams: { - fee: string; - sqrtPriceLimitX96: string; - }; - arbitrumConfig?: { - arbitrumBridgeGatewayRouter: string; - }; - lzConfig?: { - lzEndpointL1: string; - lzEndpointL2: string; - }; +type FeeConfig = { + baseFee: BigNumberish; + treasury: string; }; -type PoolInitInfo = IDistribution.PoolStruct & { - whitelistedUsers: string[]; - amounts: BigNumberish[]; +export type Config = { + feeConfig: FeeConfig; + + depositTokenExternalDeps: IL1Factory.DepositTokenExternalDepsStruct; + lzExternalDeps: IL1Factory.LzExternalDepsStruct; + arbExternalDeps: IL1Factory.ArbExternalDepsStruct; + + lzTokenExternalDeps: IL2Factory.LzExternalDepsStruct; + uniswapExternalDeps: IL2Factory.UniswapExternalDepsStruct; }; export function parseConfig(chainId: bigint): Config { @@ -57,130 +40,96 @@ export function parseConfig(chainId: bigint): Config { const config: Config = JSON.parse(readFileSync(configPath, 'utf-8')) as Config; - if (config.cap == undefined) { - throw new Error(`Invalid 'cap' value.`); - } + validateFeeConfig(config.feeConfig); - if (config.chainsConfig == undefined) { - throw new Error(`Invalid 'chainsConfig' value.`); - } - if (config.chainsConfig.receiverChainId == undefined) { - throw new Error(`Invalid 'chainsConfig.receiverChainId' value.`); - } - if (config.chainsConfig.senderChainId == undefined) { - throw new Error(`Invalid 'chainsConfig.senderChainId' value.`); - } + validateDepositTokenExternalDeps(config.depositTokenExternalDeps); + validateLzExternalDeps(config.lzExternalDeps); + validateArbExternalDeps(config.arbExternalDeps); - if (config.pools != undefined) { - validatePools(config.pools); - } + validateLzTokenExternalDeps(config.lzTokenExternalDeps); + validateUniswapExternalDeps(config.uniswapExternalDeps); + + return config; +} - if (config.L1 != undefined) { - if (config.L1.stEth == undefined) { - nonZeroAddr(config.L1.stEth, 'L1.stEth'); - } +function nonNumber(value: BigNumberish) { + return !(typeof value === 'number' || typeof value === 'bigint' || typeof BigInt(value) === 'bigint'); +} - if (config.L1.wStEth == undefined) { - nonZeroAddr(config.L1.wStEth, 'L1.wStEth'); - } +function nonZeroAddr(filedDataRaw: any, filedName: string) { + if (isZeroAddr(filedDataRaw)) { + throw new Error(`Invalid ${filedName} filed.`); } +} - if (config.L2 != undefined) { - if (config.L2.swapRouter == undefined) { - nonZeroAddr(config.L2.swapRouter, 'L2.swapRouter'); - } +function isZeroAddr(filedDataRaw: string | undefined) { + return isEmptyField(filedDataRaw) || filedDataRaw === ZERO_ADDR; +} - if (config.L2.nonfungiblePositionManager == undefined) { - nonZeroAddr(config.L2.nonfungiblePositionManager, 'L2.nonfungiblePositionManager'); - } +function isEmptyField(filedDataRaw: any) { + return !filedDataRaw || filedDataRaw == ''; +} - if (config.L2.wStEth == undefined) { - nonZeroAddr(config.L2.wStEth, 'L2.wStEth'); - } +function validateFeeConfig(feeConfig: FeeConfig) { + if (feeConfig === undefined) { + throw new Error(`Invalid feeConfig.`); } - - if ( - config.swapParams == undefined || - nonNumber(config.swapParams.fee) || - nonNumber(config.swapParams.sqrtPriceLimitX96) - ) { - throw new Error('Invalid `swapParams`'); + if (nonNumber(feeConfig.baseFee)) { + throw new Error(`Invalid feeConfig.baseFee.`); } + nonZeroAddr(feeConfig.treasury, 'feeConfig.treasury'); +} - if (config.lzConfig != undefined) { - if (config.lzConfig.lzEndpointL1 == undefined) { - throw new Error('Invalid `lzConfig.lzEndpointL1`'); - } - if (config.lzConfig.lzEndpointL2 == undefined) { - throw new Error('Invalid `lzConfig.lzEndpointL2`'); - } +function validateDepositTokenExternalDeps(depositTokenExternalDeps: IL1Factory.DepositTokenExternalDepsStruct) { + if (depositTokenExternalDeps === undefined) { + throw new Error(`Invalid depositTokenExternalDeps.`); } + nonZeroAddr(depositTokenExternalDeps.token, 'depositTokenExternalDeps.token'); + nonZeroAddr(depositTokenExternalDeps.wToken, 'depositTokenExternalDeps.wToken'); +} - if (config.arbitrumConfig != undefined) { - if (config.arbitrumConfig.arbitrumBridgeGatewayRouter == undefined) { - throw new Error('Invalid `arbitrumConfig.arbitrumBridgeGatewayRouter`'); - } +function validateLzExternalDeps(lzExternalDeps: IL1Factory.LzExternalDepsStruct) { + if (lzExternalDeps === undefined) { + throw new Error(`Invalid lzExternalDeps.`); } - return config; -} + nonZeroAddr(lzExternalDeps.endpoint, 'lzExternalDeps.endpoint'); -function nonNumber(value: BigNumberish) { - return !(typeof value === 'number' || typeof value === 'bigint' || typeof BigInt(value) === 'bigint'); + if (isEmptyField(lzExternalDeps.zroPaymentAddress)) { + throw new Error(`Invalid lzExternalDeps.zroPaymentAddress.`); + } + if (isEmptyField(lzExternalDeps.adapterParams)) { + throw new Error(`Invalid lzExternalDeps.adapterParams.`); + } + if (nonNumber(lzExternalDeps.destinationChainId)) { + throw new Error(`Invalid lzExternalDeps.destinationChainId.`); + } } -function nonZeroAddr(filedDataRaw: string | undefined, filedName: string) { - if (isZeroAddr(filedDataRaw)) { - throw new Error(`Invalid ${filedName} filed.`); +function validateArbExternalDeps(arbExternalDeps: IL1Factory.ArbExternalDepsStruct) { + if (arbExternalDeps === undefined) { + throw new Error(`Invalid arbExternalDeps.`); } + nonZeroAddr(arbExternalDeps.endpoint, 'arbExternalDeps.endpoint'); } -function isZeroAddr(filedDataRaw: string | undefined) { - return isEmptyField(filedDataRaw) || filedDataRaw === ZERO_ADDR; -} +function validateLzTokenExternalDeps(lzTokenExternalDeps: IL2Factory.LzExternalDepsStruct) { + if (lzTokenExternalDeps === undefined) { + throw new Error(`Invalid lzTokenExternalDeps.`); + } -function isEmptyField(filedDataRaw: string | undefined) { - return !filedDataRaw || filedDataRaw == ''; + nonZeroAddr(lzTokenExternalDeps.endpoint, 'lzTokenExternalDeps.endpoint'); + nonZeroAddr(lzTokenExternalDeps.oftEndpoint, 'lzTokenExternalDeps.oftEndpoint'); + if (nonNumber(lzTokenExternalDeps.senderChainId)) { + throw new Error(`Invalid lzTokenExternalDeps.senderChainId.`); + } } -function validatePools(pools: PoolInitInfo[]) { - pools.forEach((pool: PoolInitInfo) => { - if ( - nonNumber(pool.payoutStart) || - nonNumber(pool.decreaseInterval) || - nonNumber(pool.withdrawLockPeriod) || - nonNumber(pool.claimLockPeriod) || - typeof pool.isPublic !== 'boolean' || - nonNumber(pool.initialReward) || - nonNumber(pool.rewardDecrease) || - nonNumber(pool.minimalStake) - ) { - throw new Error(`Invalid pool.`); - } - - if (pool.whitelistedUsers != undefined) { - if (pool.amounts == undefined || pool.amounts.length != pool.whitelistedUsers.length) { - throw new Error(`Invalid pool amounts.`); - } - - if (pool.isPublic) { - if ((pool.whitelistedUsers && pool.whitelistedUsers.length > 0) || (pool.amounts && pool.amounts.length > 0)) { - throw new Error(`Invalid pool whitelistedUsers.`); - } - } else { - pool.whitelistedUsers.forEach((user: string) => { - nonZeroAddr(user, 'whitelistedUsers'); - }); - pool.amounts.forEach((amount: BigNumberish) => { - if (nonNumber(amount)) { - throw new Error(`Invalid pool amounts.`); - } - }); - } - } else { - if (pool.amounts != undefined) { - throw new Error(`Invalid pool amounts.`); - } - } - }); +function validateUniswapExternalDeps(uniswapExternalDeps: IL2Factory.UniswapExternalDepsStruct) { + if (uniswapExternalDeps === undefined) { + throw new Error(`Invalid uniswapExternalDeps.`); + } + + nonZeroAddr(uniswapExternalDeps.router, 'uniswapExternalDeps.router'); + nonZeroAddr(uniswapExternalDeps.nonfungiblePositionManager, 'uniswapExternalDeps.nonfungiblePositionManager'); } diff --git a/hardhat.config.ts b/hardhat.config.ts index a2ba9d2..f3b7323 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -33,7 +33,8 @@ const config: HardhatUserConfig = { hardhat: { initialDate: '1970-01-01T00:00:00Z', // forking: { - // url: `https://mainnet.infura.io/v3/${process.env.INFURA_KEY}`, + // url: `https://eth-mainnet.g.alchemy.com/v2/${process.env.ALCHEMY_KEY}`, + // blockNumber: 19842819, // }, // forking: { // url: `https://arbitrum-sepolia.infura.io/v3/${process.env.INFURA_KEY}`, diff --git a/package-lock.json b/package-lock.json index 403ee51..8038342 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,58 +16,49 @@ "@layerzerolabs/solidity-examples": "^1.0.0", "@openzeppelin/contracts": "4.9.2", "@openzeppelin/contracts-upgradeable": "4.9.2", - "@solarity/solidity-lib": "2.5.9", + "@solarity/solidity-lib": "2.7.2", "@uniswap/v3-core": "^1.0.1", "@uniswap/v3-periphery": "^1.4.4", "dotenv": "^10.0.0", - "hardhat": "^2.17.4", - "typechain": "^8.3.1" + "hardhat": "^2.22.4", + "typechain": "^8.3.2" }, "devDependencies": { - "@metamask/eth-sig-util": "^7.0.0", - "@nomicfoundation/hardhat-chai-matchers": "^2.0.2", - "@nomicfoundation/hardhat-ethers": "^3.0.4", - "@nomicfoundation/hardhat-network-helpers": "^1.0.9", + "@metamask/eth-sig-util": "^7.0.2", + "@nomicfoundation/hardhat-chai-matchers": "^2.0.6", + "@nomicfoundation/hardhat-ethers": "^3.0.6", + "@nomicfoundation/hardhat-network-helpers": "^1.0.10", "@nomiclabs/hardhat-truffle5": "^2.0.7", "@nomiclabs/hardhat-web3": "^2.0.0", - "@solarity/hardhat-markup": "^1.0.2", + "@solarity/hardhat-markup": "^1.0.7", "@solarity/hardhat-migrate": "2.1.7", "@trivago/prettier-plugin-sort-imports": "^4.3.0", - "@typechain/ethers-v6": "^0.5.0", - "@typechain/hardhat": "^9.0.0", - "@typechain/truffle-v5": "^8.0.6", - "@typechain/web3-v1": "^6.0.6", - "@types/chai": "^4.3.6", - "@types/mocha": "^10.0.2", - "@types/node": "^18.16.0", - "@typescript-eslint/eslint-plugin": "^6.13.1", - "@typescript-eslint/parser": "^6.13.1", - "chai": "^4.3.10", - "eslint": "^8.54.0", - "eslint-config-prettier": "^9.0.0", - "eslint-plugin-prettier": "^5.0.1", + "@typechain/ethers-v6": "^0.5.1", + "@typechain/hardhat": "^9.1.0", + "@typechain/truffle-v5": "^8.0.7", + "@typechain/web3-v1": "^6.0.7", + "@types/chai": "^4.3.16", + "@types/mocha": "^10.0.6", + "@types/node": "^18.19.33", + "@typescript-eslint/eslint-plugin": "^6.21.0", + "@typescript-eslint/parser": "^6.21.0", + "chai": "^4.4.1", + "eslint": "^8.57.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-prettier": "^5.1.3", "hardhat-contract-sizer": "^2.10.0", - "hardhat-gas-reporter": "^1.0.9", - "husky": "^8.0.2", - "mocha": "^10.2.0", - "prettier": "^3.1.0", + "hardhat-gas-reporter": "^1.0.10", + "husky": "^8.0.3", + "mocha": "^10.4.0", + "prettier": "^3.2.5", "prettier-plugin-solidity": "^1.3.1", - "solhint": "^4.0.0", + "solhint": "^4.5.4", "solhint-plugin-prettier": "^0.1.0", - "solidity-coverage": "^0.8.5", - "ts-node": "^10.9.1", + "solidity-coverage": "^0.8.12", + "ts-node": "^10.9.2", "tsconfig-paths": "^4.2.0", - "typescript": "^5.2.2", - "web3": "^1.7.5" - } - }, - "node_modules/@aashutoshrathi/word-wrap": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", - "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", - "dev": true, - "engines": { - "node": ">=0.10.0" + "typescript": "^5.4.5", + "web3": "^1.10.4" } }, "node_modules/@adraffy/ens-normalize": { @@ -343,15 +334,15 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "node_modules/@aws-sdk/types": { - "version": "3.535.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.535.0.tgz", - "integrity": "sha512-aY4MYfduNj+sRR37U7XxYR8wemfbKP6lx00ze2M2uubn7mZotuVrWYAafbMSXrdEMSToE5JDhr28vArSOoLcSg==", + "version": "3.575.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.575.0.tgz", + "integrity": "sha512-XrnolQGs0wXxdgNudirR14OgNOarH7WUif38+2Pd4onZH+L7XoILem0EgA1tRpgFpw2pFHlZCNaAHDNSBEal7g==", "dependencies": { - "@smithy/types": "^2.12.0", + "@smithy/types": "^3.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/types/node_modules/tslib": { @@ -417,13 +408,13 @@ } }, "node_modules/@babel/helper-function-name/node_modules/@babel/types": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz", - "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.5.tgz", + "integrity": "sha512-6mQNsaLeXTw0nxYUYu+NSa4Hx4BlF1x1x8/PMFbiR+GBSr+2DkECc69b8hgy2frEodNcvPffeH8YfWd3LI6jhQ==", "dev": true, "dependencies": { - "@babel/helper-string-parser": "^7.23.4", - "@babel/helper-validator-identifier": "^7.22.20", + "@babel/helper-string-parser": "^7.24.1", + "@babel/helper-validator-identifier": "^7.24.5", "to-fast-properties": "^2.0.0" }, "engines": { @@ -443,13 +434,13 @@ } }, "node_modules/@babel/helper-hoist-variables/node_modules/@babel/types": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz", - "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.5.tgz", + "integrity": "sha512-6mQNsaLeXTw0nxYUYu+NSa4Hx4BlF1x1x8/PMFbiR+GBSr+2DkECc69b8hgy2frEodNcvPffeH8YfWd3LI6jhQ==", "dev": true, "dependencies": { - "@babel/helper-string-parser": "^7.23.4", - "@babel/helper-validator-identifier": "^7.22.20", + "@babel/helper-string-parser": "^7.24.1", + "@babel/helper-validator-identifier": "^7.24.5", "to-fast-properties": "^2.0.0" }, "engines": { @@ -457,25 +448,25 @@ } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.5.tgz", + "integrity": "sha512-5CHncttXohrHk8GWOFCcCl4oRD9fKosWlIRgWm4ql9VYioKm52Mk2xsmoohvm7f3JoiLSM5ZgJuRaf5QZZYd3Q==", "dev": true, "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-split-export-declaration/node_modules/@babel/types": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz", - "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.5.tgz", + "integrity": "sha512-6mQNsaLeXTw0nxYUYu+NSa4Hx4BlF1x1x8/PMFbiR+GBSr+2DkECc69b8hgy2frEodNcvPffeH8YfWd3LI6jhQ==", "dev": true, "dependencies": { - "@babel/helper-string-parser": "^7.23.4", - "@babel/helper-validator-identifier": "^7.22.20", + "@babel/helper-string-parser": "^7.24.1", + "@babel/helper-validator-identifier": "^7.24.5", "to-fast-properties": "^2.0.0" }, "engines": { @@ -492,21 +483,21 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.5.tgz", + "integrity": "sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.24.2", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.2.tgz", - "integrity": "sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.5.tgz", + "integrity": "sha512-8lLmua6AVh/8SLJRRVD6V8p73Hir9w5mJrhE+IPpILG31KKlI9iz5zmBYKcWPS59qSfgP9RaSBQSHHE81WKuEw==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.22.20", + "@babel/helper-validator-identifier": "^7.24.5", "chalk": "^2.4.2", "js-tokens": "^4.0.0", "picocolors": "^1.0.0" @@ -516,9 +507,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.4.tgz", - "integrity": "sha512-zTvEBcghmeBma9QIGunWevvBAp4/Qu9Bdq+2k0Ot4fVMD6v3dsC9WOcRSKk7tRRyBM/53yKMJko9xOatGQAwSg==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.5.tgz", + "integrity": "sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -528,9 +519,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.4.tgz", - "integrity": "sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.5.tgz", + "integrity": "sha512-Nms86NXrsaeU9vbBJKni6gXiEXZ4CVpYVzEjDH9Sb8vmZ3UljyA1GSOJl/6LGPO8EHLuSF9H+IxNXHPX8QHJ4g==", "dev": true, "dependencies": { "regenerator-runtime": "^0.14.0" @@ -554,13 +545,13 @@ } }, "node_modules/@babel/template/node_modules/@babel/types": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz", - "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.5.tgz", + "integrity": "sha512-6mQNsaLeXTw0nxYUYu+NSa4Hx4BlF1x1x8/PMFbiR+GBSr+2DkECc69b8hgy2frEodNcvPffeH8YfWd3LI6jhQ==", "dev": true, "dependencies": { - "@babel/helper-string-parser": "^7.23.4", - "@babel/helper-validator-identifier": "^7.22.20", + "@babel/helper-string-parser": "^7.24.1", + "@babel/helper-validator-identifier": "^7.24.5", "to-fast-properties": "^2.0.0" }, "engines": { @@ -589,12 +580,12 @@ } }, "node_modules/@babel/traverse/node_modules/@babel/generator": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.4.tgz", - "integrity": "sha512-Xd6+v6SnjWVx/nus+y0l1sxMOTOMBkyL4+BIdbALyatQnAe/SRVjANeDPSCYaX+i1iJmuGSKf3Z+E+V/va1Hvw==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.5.tgz", + "integrity": "sha512-x32i4hEXvr+iI0NEoEfDKzlemF8AmtOP8CcrRaEcpzysWuoEb1KknpcvMsHKPONoKZiDuItklgWhB18xEhr9PA==", "dev": true, "dependencies": { - "@babel/types": "^7.24.0", + "@babel/types": "^7.24.5", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" @@ -604,13 +595,13 @@ } }, "node_modules/@babel/traverse/node_modules/@babel/types": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz", - "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.5.tgz", + "integrity": "sha512-6mQNsaLeXTw0nxYUYu+NSa4Hx4BlF1x1x8/PMFbiR+GBSr+2DkECc69b8hgy2frEodNcvPffeH8YfWd3LI6jhQ==", "dev": true, "dependencies": { - "@babel/helper-string-parser": "^7.23.4", - "@babel/helper-validator-identifier": "^7.22.20", + "@babel/helper-string-parser": "^7.24.1", + "@babel/helper-validator-identifier": "^7.24.5", "to-fast-properties": "^2.0.0" }, "engines": { @@ -1990,9 +1981,9 @@ } }, "node_modules/@layerzerolabs/lz-evm-protocol-v2": { - "version": "2.1.27", - "resolved": "https://registry.npmjs.org/@layerzerolabs/lz-evm-protocol-v2/-/lz-evm-protocol-v2-2.1.27.tgz", - "integrity": "sha512-kJlPYILW/VLxeeWagFYJpP/4EFeCw7Yuqp+qpv/+lnGDSXflvanGVnCApBJtEmirMva4E0nEkTFd0x9rfu/1Dg==", + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/@layerzerolabs/lz-evm-protocol-v2/-/lz-evm-protocol-v2-2.3.7.tgz", + "integrity": "sha512-Nhe6XIqJNXT22yBcNxwigQAEFesUc3vYZircDmncWhD6iWvsJiul36B1sfON+8mMR2zjXKBXMk+Z8eGdSCdmKg==", "peerDependencies": { "@openzeppelin/contracts": "^4.8.1 || ^5.0.0", "@openzeppelin/contracts-upgradeable": "^4.8.1 || ^5.0.0", @@ -2450,17 +2441,17 @@ } }, "node_modules/@metamask/eth-sig-util": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@metamask/eth-sig-util/-/eth-sig-util-7.0.1.tgz", - "integrity": "sha512-59GSrMyFH2fPfu7nKeIQdZ150zxXNNhAQIUaFRUW+MGtVA4w/ONbiQobcRBLi+jQProfIyss51G8pfLPcQ0ylg==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@metamask/eth-sig-util/-/eth-sig-util-7.0.2.tgz", + "integrity": "sha512-DhTDMNEtED0ihIc4Tysm6qUJTvArCdgSTeeJWdo526W/cAk5mrSAvEYYgv8idAiBumDtcPWGimMTaB7MvY64bg==", "dev": true, "dependencies": { "@ethereumjs/util": "^8.1.0", "@metamask/abi-utils": "^2.0.2", "@metamask/utils": "^8.1.0", + "@scure/base": "~1.1.3", "ethereum-cryptography": "^2.1.2", - "tweetnacl": "^1.0.3", - "tweetnacl-util": "^0.15.1" + "tweetnacl": "^1.0.3" }, "engines": { "node": "^16.20 || ^18.16 || >=20" @@ -2569,28 +2560,26 @@ } }, "node_modules/@nomicfoundation/edr": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr/-/edr-0.3.3.tgz", - "integrity": "sha512-zP+e+3B1nEUx6bW5BPnIzCQbkhmYfdMBJdiVggTqqTfAA82sOkdOG7wsOMcz5qF3fYfx/irNRM1kgc9HVFIbpQ==", + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr/-/edr-0.3.7.tgz", + "integrity": "sha512-v2JFWnFKRsnOa6PDUrD+sr8amcdhxnG/YbL7LzmgRGU1odWEyOF4/EwNeUajQr4ZNKVWrYnJ6XjydXtUge5OBQ==", "engines": { "node": ">= 18" }, "optionalDependencies": { - "@nomicfoundation/edr-darwin-arm64": "0.3.3", - "@nomicfoundation/edr-darwin-x64": "0.3.3", - "@nomicfoundation/edr-linux-arm64-gnu": "0.3.3", - "@nomicfoundation/edr-linux-arm64-musl": "0.3.3", - "@nomicfoundation/edr-linux-x64-gnu": "0.3.3", - "@nomicfoundation/edr-linux-x64-musl": "0.3.3", - "@nomicfoundation/edr-win32-arm64-msvc": "0.3.3", - "@nomicfoundation/edr-win32-ia32-msvc": "0.3.3", - "@nomicfoundation/edr-win32-x64-msvc": "0.3.3" + "@nomicfoundation/edr-darwin-arm64": "0.3.7", + "@nomicfoundation/edr-darwin-x64": "0.3.7", + "@nomicfoundation/edr-linux-arm64-gnu": "0.3.7", + "@nomicfoundation/edr-linux-arm64-musl": "0.3.7", + "@nomicfoundation/edr-linux-x64-gnu": "0.3.7", + "@nomicfoundation/edr-linux-x64-musl": "0.3.7", + "@nomicfoundation/edr-win32-x64-msvc": "0.3.7" } }, "node_modules/@nomicfoundation/edr-darwin-arm64": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-darwin-arm64/-/edr-darwin-arm64-0.3.3.tgz", - "integrity": "sha512-E9VGsUD+1Ga4mn/5ooHsMi8JEfhZbKP6CXN/BhJ8kXbIC10NqTD1RuhCKGRtYq4vqH/3Nfq25Xg8E8RWOF4KBQ==", + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-darwin-arm64/-/edr-darwin-arm64-0.3.7.tgz", + "integrity": "sha512-6tK9Lv/lSfyBvpEQ4nsTfgxyDT1y1Uv/x8Wa+aB+E8qGo3ToexQ1BMVjxJk6PChXCDOWxB3B4KhqaZFjdhl3Ow==", "cpu": [ "arm64" ], @@ -2603,9 +2592,9 @@ } }, "node_modules/@nomicfoundation/edr-darwin-x64": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-darwin-x64/-/edr-darwin-x64-0.3.3.tgz", - "integrity": "sha512-vkZXZ1ydPg+Ijb2iyqENA+KCkxGTCUWG5itCSliiA0Li2YE7ujDMGhheEpFp1WVlZadviz0bfk1rZXbCqlirpg==", + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-darwin-x64/-/edr-darwin-x64-0.3.7.tgz", + "integrity": "sha512-1RrQ/1JPwxrYO69e0tglFv5H+ggour5Ii3bb727+yBpBShrxtOTQ7fZyfxA5h62LCN+0Z9wYOPeQ7XFcVurMaQ==", "cpu": [ "x64" ], @@ -2618,9 +2607,9 @@ } }, "node_modules/@nomicfoundation/edr-linux-arm64-gnu": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-arm64-gnu/-/edr-linux-arm64-gnu-0.3.3.tgz", - "integrity": "sha512-gdIg0Yj1qqS9wVuywc5B/+DqKylfUGB6/CQn/shMqwAfsAVAVpchkhy66PR+REEx7fh/GkNctxLlENXPeLzDiA==", + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-arm64-gnu/-/edr-linux-arm64-gnu-0.3.7.tgz", + "integrity": "sha512-ds/CKlBoVXIihjhflhgPn13EdKWed6r5bgvMs/YwRqT5wldQAQJZWAfA2+nYm0Yi2gMGh1RUpBcfkyl4pq7G+g==", "cpu": [ "arm64" ], @@ -2633,9 +2622,9 @@ } }, "node_modules/@nomicfoundation/edr-linux-arm64-musl": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-arm64-musl/-/edr-linux-arm64-musl-0.3.3.tgz", - "integrity": "sha512-AXZ08MFvhNeBZbOBNmz1SJ/DMrMOE2mHEJtaNnsctlxIunjxfrWww4q+WXB34jbr9iaVYYlPsaWe5sueuw6s3Q==", + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-arm64-musl/-/edr-linux-arm64-musl-0.3.7.tgz", + "integrity": "sha512-e29udiRaPujhLkM3+R6ju7QISrcyOqpcaxb2FsDWBkuD7H8uU9JPZEyyUIpEp5uIY0Jh1eEJPKZKIXQmQAEAuw==", "cpu": [ "arm64" ], @@ -2648,9 +2637,9 @@ } }, "node_modules/@nomicfoundation/edr-linux-x64-gnu": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-x64-gnu/-/edr-linux-x64-gnu-0.3.3.tgz", - "integrity": "sha512-xElOs1U+E6lBLtv1mnJ+E8nr2MxZgKiLo8bZAgBboy9odYtmkDVwhMjtsFKSuZbGxFtsSyGRT4cXw3JAbtUDeA==", + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-x64-gnu/-/edr-linux-x64-gnu-0.3.7.tgz", + "integrity": "sha512-/xkjmTyv+bbJ4akBCW0qzFKxPOV4AqLOmqurov+s9umHb16oOv72osSa3SdzJED2gHDaKmpMITT4crxbar4Axg==", "cpu": [ "x64" ], @@ -2663,9 +2652,9 @@ } }, "node_modules/@nomicfoundation/edr-linux-x64-musl": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-x64-musl/-/edr-linux-x64-musl-0.3.3.tgz", - "integrity": "sha512-2Fe6gwm1RAGQ/PfMYiaSba2OrFp8zzYWh+am9lYObOFjV9D+A1zhIzfy0UC74glPks5eV8eY4pBPrVR042m2Nw==", + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-x64-musl/-/edr-linux-x64-musl-0.3.7.tgz", + "integrity": "sha512-QwBP9xlmsbf/ldZDGLcE4QiAb8Zt46E/+WLpxHBATFhGa7MrpJh6Zse+h2VlrT/SYLPbh2cpHgSmoSlqVxWG9g==", "cpu": [ "x64" ], @@ -2677,40 +2666,10 @@ "node": ">= 18" } }, - "node_modules/@nomicfoundation/edr-win32-arm64-msvc": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-win32-arm64-msvc/-/edr-win32-arm64-msvc-0.3.3.tgz", - "integrity": "sha512-8NHyxIsFrl0ufSQ/ErqF2lKIa/gz1gaaa1a2vKkDEqvqCUcPhBTYhA5NHgTPhLETFTnCFr0z+YbctFCyjh4qrA==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nomicfoundation/edr-win32-ia32-msvc": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-win32-ia32-msvc/-/edr-win32-ia32-msvc-0.3.3.tgz", - "integrity": "sha512-0F6hM0kGia4dQVb/kauho9JcP1ozWisY2/She+ISR5ceuhzmAwQJluM0g+0TYDME0LtxBxiMPq/yPiZMQeq31w==", - "cpu": [ - "ia32" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 18" - } - }, "node_modules/@nomicfoundation/edr-win32-x64-msvc": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-win32-x64-msvc/-/edr-win32-x64-msvc-0.3.3.tgz", - "integrity": "sha512-d75q1uaMb6z9i+GQZoblbOfFBvlBnWc+5rB13UWRkCOJSnoYwyFWhGJx5GeM59gC7aIblc5VD9qOAhHuvM9N+w==", + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-win32-x64-msvc/-/edr-win32-x64-msvc-0.3.7.tgz", + "integrity": "sha512-j/80DEnkxrF2ewdbk/gQ2EOPvgF0XSsg8D0o4+6cKhUVAW6XwtWKzIphNL6dyD2YaWEPgIrNvqiJK/aln0ww4Q==", "cpu": [ "x64" ], @@ -2846,9 +2805,9 @@ } }, "node_modules/@nomicfoundation/hardhat-ethers": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-ethers/-/hardhat-ethers-3.0.5.tgz", - "integrity": "sha512-RNFe8OtbZK6Ila9kIlHp0+S80/0Bu/3p41HUpaRIoHLm6X3WekTd83vob3rE54Duufu1edCiBDxspBzi2rxHHw==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-ethers/-/hardhat-ethers-3.0.6.tgz", + "integrity": "sha512-/xzkFQAaHQhmIAYOQmvHBPwL+NkwLzT9gRZBsgWUYeV+E6pzXsBQsHfRYbAZ3XEYare+T7S+5Tg/1KDJgepSkA==", "dev": true, "dependencies": { "debug": "^4.1.1", @@ -3299,9 +3258,9 @@ } }, "node_modules/@openzeppelin/upgrades-core": { - "version": "1.32.5", - "resolved": "https://registry.npmjs.org/@openzeppelin/upgrades-core/-/upgrades-core-1.32.5.tgz", - "integrity": "sha512-R0wprsyJ4xWiRW05kaTfZZkRVpG2g0af3/hpjE7t2mX0Eb2n40MQLokTwqIk4LDzpp910JfLSpB0vBuZ6WNPog==", + "version": "1.33.1", + "resolved": "https://registry.npmjs.org/@openzeppelin/upgrades-core/-/upgrades-core-1.33.1.tgz", + "integrity": "sha512-YRxIRhTY1b+j7+NUUu8Uuem5ugxKexEMVd8dBRWNgWeoN1gS1OCrhgUg0ytL+54vzQ+SGWZDfNnzjVuI1Cj1Zw==", "dependencies": { "cbor": "^9.0.0", "chalk": "^4.1.0", @@ -3799,14 +3758,14 @@ } }, "node_modules/@smithy/types": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-2.12.0.tgz", - "integrity": "sha512-QwYgloJ0sVNBeBuBs65cIkTbfzV/Q6ZNPCJ99EICFEdJYG50nGIY/uYXp+TbsdJReIuPr0a0kXmCvren3MbRRw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.0.0.tgz", + "integrity": "sha512-VvWuQk2RKFuOr98gFhjca7fkBS+xLLURT8bUjk5XQoV0ZLm7WPwWPPY3/AwzTLuUBDeoKDCthfe1AsTUWaSEhw==", "dependencies": { "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/types/node_modules/tslib": { @@ -3860,15 +3819,101 @@ "typechain": "^8.0.0" } }, + "node_modules/@solarity/hardhat-migrate/node_modules/@noble/curves": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", + "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", + "dev": true, + "dependencies": { + "@noble/hashes": "1.3.2" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@solarity/hardhat-migrate/node_modules/@noble/hashes": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", + "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", + "dev": true, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@solarity/hardhat-migrate/node_modules/@nomicfoundation/hardhat-ethers": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-ethers/-/hardhat-ethers-3.0.5.tgz", + "integrity": "sha512-RNFe8OtbZK6Ila9kIlHp0+S80/0Bu/3p41HUpaRIoHLm6X3WekTd83vob3rE54Duufu1edCiBDxspBzi2rxHHw==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "lodash.isequal": "^4.5.0" + }, + "peerDependencies": { + "ethers": "^6.1.0", + "hardhat": "^2.0.0" + } + }, + "node_modules/@solarity/hardhat-migrate/node_modules/@types/node": { + "version": "18.15.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.13.tgz", + "integrity": "sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q==", + "dev": true + }, + "node_modules/@solarity/hardhat-migrate/node_modules/ethers": { + "version": "6.11.1", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.11.1.tgz", + "integrity": "sha512-mxTAE6wqJQAbp5QAe/+o+rXOID7Nw91OZXvgpjDa1r4fAbq2Nu314oEZSbjoRLacuCzs7kUC3clEvkCQowffGg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/ethers-io/" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@adraffy/ens-normalize": "1.10.1", + "@noble/curves": "1.2.0", + "@noble/hashes": "1.3.2", + "@types/node": "18.15.13", + "aes-js": "4.0.0-beta.5", + "tslib": "2.4.0", + "ws": "8.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@solarity/solidity-lib": { - "version": "2.5.9", - "resolved": "https://registry.npmjs.org/@solarity/solidity-lib/-/solidity-lib-2.5.9.tgz", - "integrity": "sha512-zwUu3HR0oCOyAK2MaVoX38fpzUJxTFuif/ICtuRZcc1JIzLvRWQ7MnrbQVXWxjTL/Z1brNAqZa3wEI2U5+w01w==", + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/@solarity/solidity-lib/-/solidity-lib-2.7.2.tgz", + "integrity": "sha512-P08xkimAcR5dG2ncqA9OEjmwNwQtJeH036wmkdpp9TDESo2KGPwGwAlcy7RpfTellE8RFDuAvOBj7sedFCkeOg==", "dependencies": { - "@openzeppelin/contracts": "4.9.2", - "@openzeppelin/contracts-upgradeable": "4.9.2" + "@openzeppelin/contracts": "4.9.5", + "@openzeppelin/contracts-upgradeable": "4.9.5", + "@uniswap/v2-core": "1.0.1", + "@uniswap/v2-periphery": "1.1.0-beta.0", + "@uniswap/v3-core": "1.0.1", + "@uniswap/v3-periphery": "1.4.4" } }, + "node_modules/@solarity/solidity-lib/node_modules/@openzeppelin/contracts": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-4.9.5.tgz", + "integrity": "sha512-ZK+W5mVhRppff9BE6YdR8CC52C8zAvsVAiWhEtQ5+oNxFE6h1WdeWo+FJSF8KKvtxxVYZ7MTP/5KoVpAU3aSWg==" + }, + "node_modules/@solarity/solidity-lib/node_modules/@openzeppelin/contracts-upgradeable": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-4.9.5.tgz", + "integrity": "sha512-f7L1//4sLlflAN7fVzJLoRedrf5Na3Oal5PZfIq55NFcVZ90EpV1q5xOvL4lFvg3MNICSDr2hH0JUBxwlxcoPg==" + }, "node_modules/@solidity-parser/parser": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.14.5.tgz", @@ -6725,9 +6770,9 @@ } }, "node_modules/@types/chai": { - "version": "4.3.14", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.14.tgz", - "integrity": "sha512-Wj71sXE4Q4AkGdG9Tvq1u/fquNz9EdG4LIJMwVVII7ashjD/8cf8fyIfJAjRr6YcsXnSE8cOGQPq1gqeR8z+3w==", + "version": "4.3.16", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.16.tgz", + "integrity": "sha512-PatH4iOdyh3MyWtmHVFXLWCCIhUbopaltqddG9BzB+gMIzee2MJrvd+jouii9Z3wzQJruGWAm7WOMjgfG8hQlQ==", "dev": true }, "node_modules/@types/chai-as-promised": { @@ -6790,9 +6835,9 @@ } }, "node_modules/@types/express-serve-static-core": { - "version": "4.17.43", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.43.tgz", - "integrity": "sha512-oaYtiBirUOPQGSWNGPWnzyAFJ0BP3cwvN4oWZQY+zUBwpVIGsKUkpBpSztp74drYcjavs7SKFZ4DX1V2QeN8rg==", + "version": "4.19.0", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.0.tgz", + "integrity": "sha512-bGyep3JqPCRry1wq+O5n7oiBgGWmeIJXPjXXCo8EK0u8duZGSYar7cGqd3ML2JUsLGeB7fmc06KYo9fLGWqPvQ==", "dev": true, "optional": true, "peer": true, @@ -6890,9 +6935,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "18.19.29", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.29.tgz", - "integrity": "sha512-5pAX7ggTmWZdhUrhRWLPf+5oM7F80bcKVCBbr0zwEkTNzTJL2CWQjznpFgHYy6GrzkYi2Yjy7DHKoynFxqPV8g==", + "version": "18.19.33", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.33.tgz", + "integrity": "sha512-NR9+KrpSajr2qBVp/Yt5TU/rp+b5Mayi3+OlMlcg2cVCfRmcG5PWZ7S4+MG9PZ5gWBoc9Pd0BKSRViuBCRPu0A==", "dependencies": { "undici-types": "~5.26.4" } @@ -6911,9 +6956,9 @@ "integrity": "sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==" }, "node_modules/@types/qs": { - "version": "6.9.14", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.14.tgz", - "integrity": "sha512-5khscbd3SwWMhFqylJBLQ0zIu7c1K6Vz0uBIt915BI3zV0q1nfjRQD3RqSBcPaO6PHEF4ov/t9y89fSiyThlPA==" + "version": "6.9.15", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.15.tgz", + "integrity": "sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==" }, "node_modules/@types/range-parser": { "version": "1.2.7", @@ -7168,9 +7213,9 @@ "dev": true }, "node_modules/@uniswap/lib": { - "version": "4.0.1-alpha", - "resolved": "https://registry.npmjs.org/@uniswap/lib/-/lib-4.0.1-alpha.tgz", - "integrity": "sha512-f6UIliwBbRsgVLxIaBANF6w09tYqc6Y/qXdsrbEmXHyFA7ILiKrIwRFXe1yOg8M3cksgVsO9N7yuL2DdCGQKBA==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@uniswap/lib/-/lib-1.1.1.tgz", + "integrity": "sha512-2yK7sLpKIT91TiS5sewHtOa7YuM8IuBXVl4GZv2jZFys4D2sY7K5vZh6MqD25TPA95Od+0YzCVq6cTF2IKrOmg==", "engines": { "node": ">=10" } @@ -7183,6 +7228,26 @@ "node": ">=10" } }, + "node_modules/@uniswap/v2-periphery": { + "version": "1.1.0-beta.0", + "resolved": "https://registry.npmjs.org/@uniswap/v2-periphery/-/v2-periphery-1.1.0-beta.0.tgz", + "integrity": "sha512-6dkwAMKza8nzqYiXEr2D86dgW3TTavUvCR0w2Tu33bAbM8Ah43LKAzH7oKKPRT5VJQaMi1jtkGs1E8JPor1n5g==", + "dependencies": { + "@uniswap/lib": "1.1.1", + "@uniswap/v2-core": "1.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@uniswap/v2-periphery/node_modules/@uniswap/v2-core": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@uniswap/v2-core/-/v2-core-1.0.0.tgz", + "integrity": "sha512-BJiXrBGnN8mti7saW49MXwxDBRFiWemGetE58q8zgfnPPzQKq55ADltEILqOt6VFZ22kVeVKbF8gVd8aY3l7pA==", + "engines": { + "node": ">=10" + } + }, "node_modules/@uniswap/v3-core": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@uniswap/v3-core/-/v3-core-1.0.1.tgz", @@ -7211,6 +7276,14 @@ "resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-3.4.2-solc-0.7.tgz", "integrity": "sha512-W6QmqgkADuFcTLzHL8vVoNBtkwjvQRpYIAom7KiUNoLKghyx3FgH0GBjt8NRvigV1ZmMOBllvE1By1C+bi8WpA==" }, + "node_modules/@uniswap/v3-periphery/node_modules/@uniswap/lib": { + "version": "4.0.1-alpha", + "resolved": "https://registry.npmjs.org/@uniswap/lib/-/lib-4.0.1-alpha.tgz", + "integrity": "sha512-f6UIliwBbRsgVLxIaBANF6w09tYqc6Y/qXdsrbEmXHyFA7ILiKrIwRFXe1yOg8M3cksgVsO9N7yuL2DdCGQKBA==", + "engines": { + "node": ">=10" + } + }, "node_modules/@yarnpkg/lockfile": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", @@ -7401,17 +7474,17 @@ } }, "node_modules/ajv-formats/node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.13.0.tgz", + "integrity": "sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==", "dev": true, "optional": true, "peer": true, "dependencies": { - "fast-deep-equal": "^3.1.1", + "fast-deep-equal": "^3.1.3", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" + "uri-js": "^4.4.1" }, "funding": { "type": "github", @@ -8651,15 +8724,15 @@ } }, "node_modules/chai-as-promised": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.1.1.tgz", - "integrity": "sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.1.2.tgz", + "integrity": "sha512-aBDHZxRzYnUYuIAIPBH2s511DjlKPzXNlXSGFC8CwmroWQLfrW0LtE1nK3MAwwNhJPa9raEjNCmRoFpG0Hurdw==", "dev": true, "dependencies": { "check-error": "^1.0.2" }, "peerDependencies": { - "chai": ">= 2.1.2 < 5" + "chai": ">= 2.1.2 < 6" } }, "node_modules/chalk": { @@ -8892,9 +8965,9 @@ } }, "node_modules/cli-table3": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.4.tgz", - "integrity": "sha512-Lm3L0p+/npIQWNIiyF/nAn7T5dnOwR3xNTHXYEBFBFVPXzCVNZ5lqEC/1eo/EVfpDsQ1I+TX4ORPQgp+UI0CRw==", + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", + "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", "dependencies": { "string-width": "^4.2.0" }, @@ -9157,17 +9230,17 @@ } }, "node_modules/conf/node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.13.0.tgz", + "integrity": "sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==", "dev": true, "optional": true, "peer": true, "dependencies": { - "fast-deep-equal": "^3.1.1", + "fast-deep-equal": "^3.1.3", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" + "uri-js": "^4.4.1" }, "funding": { "type": "github", @@ -11059,12 +11132,12 @@ } }, "node_modules/ethereum-bloom-filters": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/ethereum-bloom-filters/-/ethereum-bloom-filters-1.0.10.tgz", - "integrity": "sha512-rxJ5OFN3RwjQxDcFP2Z5+Q9ho4eIdEmSc2ht0fCu8Se9nbXjZ7/031uXoUYJ87KHCOdVeiUuwSnoS7hmYAGVHA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ethereum-bloom-filters/-/ethereum-bloom-filters-1.1.0.tgz", + "integrity": "sha512-J1gDRkLpuGNvWYzWslBQR9cDV4nd4kfvVTE/Wy4Kkm4yb3EYRSlyi0eB/inTsSTTVyA0+HyzHgbr95Fn/Z1fSw==", "dev": true, "dependencies": { - "js-sha3": "^0.8.0" + "@noble/hashes": "^1.4.0" } }, "node_modules/ethereum-cryptography": { @@ -11207,9 +11280,9 @@ } }, "node_modules/ethers": { - "version": "6.11.1", - "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.11.1.tgz", - "integrity": "sha512-mxTAE6wqJQAbp5QAe/+o+rXOID7Nw91OZXvgpjDa1r4fAbq2Nu314oEZSbjoRLacuCzs7kUC3clEvkCQowffGg==", + "version": "6.12.1", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.12.1.tgz", + "integrity": "sha512-j6wcVoZf06nqEcBbDWkKg8Fp895SS96dSnTCjiXT+8vt2o02raTn4Lo9ERUuIVU5bAjoPYeA+7ytQFexFmLuVw==", "dev": true, "funding": [ { @@ -11221,6 +11294,7 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], + "peer": true, "dependencies": { "@adraffy/ens-normalize": "1.10.1", "@noble/curves": "1.2.0", @@ -11239,6 +11313,7 @@ "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", "dev": true, + "peer": true, "dependencies": { "@noble/hashes": "1.3.2" }, @@ -11251,6 +11326,7 @@ "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", "dev": true, + "peer": true, "engines": { "node": ">= 16" }, @@ -11262,7 +11338,8 @@ "version": "18.15.13", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.13.tgz", "integrity": "sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q==", - "dev": true + "dev": true, + "peer": true }, "node_modules/ethjs-unit": { "version": "0.1.6", @@ -16107,11 +16184,12 @@ } }, "node_modules/globalthis": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", - "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", "dependencies": { - "define-properties": "^1.1.3" + "define-properties": "^1.2.1", + "gopd": "^1.0.1" }, "engines": { "node": ">= 0.4" @@ -16296,13 +16374,13 @@ } }, "node_modules/hardhat": { - "version": "2.22.2", - "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.22.2.tgz", - "integrity": "sha512-0xZ7MdCZ5sJem4MrvpQWLR3R3zGDoHw5lsR+pBFimqwagimIOn3bWuZv69KA+veXClwI1s/zpqgwPwiFrd4Dxw==", + "version": "2.22.4", + "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.22.4.tgz", + "integrity": "sha512-09qcXJFBHQUaraJkYNr7XlmwjOj27xBB0SL2rYS024hTj9tPMbp26AFjlf5quBMO9SR4AJFg+4qWahcYcvXBuQ==", "dependencies": { "@ethersproject/abi": "^5.1.2", "@metamask/eth-sig-util": "^4.0.0", - "@nomicfoundation/edr": "^0.3.1", + "@nomicfoundation/edr": "^0.3.7", "@nomicfoundation/ethereumjs-common": "4.0.4", "@nomicfoundation/ethereumjs-tx": "5.0.4", "@nomicfoundation/ethereumjs-util": "9.0.4", @@ -17298,9 +17376,9 @@ "peer": true }, "node_modules/immutable": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.5.tgz", - "integrity": "sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw==" + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.6.tgz", + "integrity": "sha512-Ju0+lEMyzMVZarkTn/gqRpdqd5dOPaz1mCZ0SH3JV6iFw81PldE/PEB1hWVEA288HPt4WXW8O7AWxB10M+03QQ==" }, "node_modules/import-fresh": { "version": "3.3.0", @@ -18711,6 +18789,9 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "optional": true, + "peer": true, "dependencies": { "yallist": "^4.0.0" }, @@ -19365,9 +19446,9 @@ } }, "node_modules/node-gyp-build": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.0.tgz", - "integrity": "sha512-u6fs2AEUljNho3EYTJNBfImO5QTo/J/1Etd+NVdCj7qWKUSN/bSLkZwhDv7I+w/MSC6qJ4cknepkAYykDdK8og==", + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.1.tgz", + "integrity": "sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw==", "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", @@ -19644,17 +19725,17 @@ } }, "node_modules/optionator": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", - "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, "dependencies": { - "@aashutoshrathi/word-wrap": "^1.2.3", "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", - "type-check": "^0.4.0" + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" }, "engines": { "node": ">= 0.8.0" @@ -20274,9 +20355,9 @@ "dev": true }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", "dev": true }, "node_modules/picomatch": { @@ -20426,9 +20507,9 @@ } }, "node_modules/pony-cause": { - "version": "2.1.10", - "resolved": "https://registry.npmjs.org/pony-cause/-/pony-cause-2.1.10.tgz", - "integrity": "sha512-3IKLNXclQgkU++2fSi93sQ6BznFuxSLB11HdvZQ6JW/spahf/P1pAHBQEahr20rs0htZW0UDkM1HmA+nZkXKsw==", + "version": "2.1.11", + "resolved": "https://registry.npmjs.org/pony-cause/-/pony-cause-2.1.11.tgz", + "integrity": "sha512-M7LhCsdNbNgiLYiP4WjsfLUuFmCfnjdF6jKe2R9NKl4WFN+HZPGHJZ9lnLP7f9ZnKe3U9nuWD0szirmj+migUg==", "dev": true, "engines": { "node": ">=12.0.0" @@ -21006,9 +21087,9 @@ ] }, "node_modules/qs": { - "version": "6.12.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.12.0.tgz", - "integrity": "sha512-trVZiI6RMOkO476zLGaBIzszOdFPnCCXHPG9kn0yuS1uz6xdVxPfZdB3vUig9pxPFDM9BRAgz/YUIVQ1/vuiUg==", + "version": "6.12.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.12.1.tgz", + "integrity": "sha512-zWmv4RSuB9r2mYQw3zxQuHWeU+42aKi1wWig/j4ele4ygELZ7PEO6MM7rim9oAQH2A5MWfsAVf/jPvTPgCbvUQ==", "dependencies": { "side-channel": "^1.0.6" }, @@ -21902,12 +21983,9 @@ } }, "node_modules/semver": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", - "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", - "dependencies": { - "lru-cache": "^6.0.0" - }, + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", "bin": { "semver": "bin/semver.js" }, @@ -22468,9 +22546,9 @@ } }, "node_modules/solhint": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/solhint/-/solhint-4.5.2.tgz", - "integrity": "sha512-o7MNYS5QPgE6l+PTGOTAUtCzo0ZLnffQsv586hntSHBe2JbSDfkoxfhAOcjZjN4OesTgaX4UEEjCjH9y/4BP5w==", + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/solhint/-/solhint-4.5.4.tgz", + "integrity": "sha512-Cu1XiJXub2q1eCr9kkJ9VPv1sGcmj3V7Zb76B0CoezDOB9bu3DxKIFFH7ggCl9fWpEPD6xBmRLfZrYijkVmujQ==", "dev": true, "dependencies": { "@solidity-parser/parser": "^0.18.0", @@ -22682,9 +22760,9 @@ "dev": true }, "node_modules/solidity-coverage": { - "version": "0.8.11", - "resolved": "https://registry.npmjs.org/solidity-coverage/-/solidity-coverage-0.8.11.tgz", - "integrity": "sha512-yy0Yk+olovBbXn0Me8BWULmmv7A69ZKkP5aTOJGOO8u61Tu2zS989erfjtFlUjDnfWtxRAVkd8BsQD704yLWHw==", + "version": "0.8.12", + "resolved": "https://registry.npmjs.org/solidity-coverage/-/solidity-coverage-0.8.12.tgz", + "integrity": "sha512-8cOB1PtjnjFRqOgwFiD8DaUsYJtVJ6+YdXQtSZDrLGf8cdhhh8xzTtGzVTGeBf15kTv0v7lYPJlV/az7zLEPJw==", "dev": true, "dependencies": { "@ethersproject/abi": "^5.0.9", @@ -22697,7 +22775,7 @@ "global-modules": "^2.0.0", "globby": "^10.0.1", "jsonschema": "^1.2.4", - "lodash": "^4.17.15", + "lodash": "^4.17.21", "mocha": "^10.2.0", "node-emoji": "^1.10.0", "pify": "^4.0.1", @@ -23411,14 +23489,14 @@ } }, "node_modules/table/node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.13.0.tgz", + "integrity": "sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==", "dependencies": { - "fast-deep-equal": "^3.1.1", + "fast-deep-equal": "^3.1.3", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" + "uri-js": "^4.4.1" }, "funding": { "type": "github", @@ -23642,9 +23720,9 @@ } }, "node_modules/tough-cookie": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", - "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", + "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", "dev": true, "optional": true, "peer": true, @@ -24279,9 +24357,9 @@ } }, "node_modules/typescript": { - "version": "5.4.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz", - "integrity": "sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==", + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -25157,14 +25235,14 @@ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" }, "node_modules/websocket": { - "version": "1.0.34", - "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.34.tgz", - "integrity": "sha512-PRDso2sGwF6kM75QykIesBijKSVceR6jL2G8NGYyq2XrItNC2P5/qL5XeR056GhA+Ly7JMFvJb9I312mJfmqnQ==", + "version": "1.0.35", + "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.35.tgz", + "integrity": "sha512-/REy6amwPZl44DDzvRCkaI1q1bIiQB0mEFQLUrhz3z2EK91cp3n72rAjUlrTP0zV22HJIUOVHQGPxhFRjxjt+Q==", "dev": true, "dependencies": { "bufferutil": "^4.0.1", "debug": "^2.2.0", - "es5-ext": "^0.10.50", + "es5-ext": "^0.10.63", "typedarray-to-buffer": "^3.1.5", "utf-8-validate": "^5.0.2", "yaeti": "^0.0.6" @@ -25587,7 +25665,10 @@ "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "optional": true, + "peer": true }, "node_modules/yaml": { "version": "1.10.2", diff --git a/package.json b/package.json index 26743be..467c64a 100644 --- a/package.json +++ b/package.json @@ -44,48 +44,48 @@ "@layerzerolabs/solidity-examples": "^1.0.0", "@openzeppelin/contracts": "4.9.2", "@openzeppelin/contracts-upgradeable": "4.9.2", - "@solarity/solidity-lib": "2.5.9", + "@solarity/solidity-lib": "2.7.2", "@uniswap/v3-core": "^1.0.1", "@uniswap/v3-periphery": "^1.4.4", "dotenv": "^10.0.0", - "hardhat": "^2.17.4", - "typechain": "^8.3.1" + "hardhat": "^2.22.4", + "typechain": "^8.3.2" }, "devDependencies": { - "@metamask/eth-sig-util": "^7.0.0", - "@nomicfoundation/hardhat-chai-matchers": "^2.0.2", - "@nomicfoundation/hardhat-ethers": "^3.0.4", - "@nomicfoundation/hardhat-network-helpers": "^1.0.9", + "@metamask/eth-sig-util": "^7.0.2", + "@nomicfoundation/hardhat-chai-matchers": "^2.0.6", + "@nomicfoundation/hardhat-ethers": "^3.0.6", + "@nomicfoundation/hardhat-network-helpers": "^1.0.10", "@nomiclabs/hardhat-truffle5": "^2.0.7", "@nomiclabs/hardhat-web3": "^2.0.0", - "@solarity/hardhat-markup": "^1.0.2", + "@solarity/hardhat-markup": "^1.0.7", "@solarity/hardhat-migrate": "2.1.7", "@trivago/prettier-plugin-sort-imports": "^4.3.0", - "@typechain/ethers-v6": "^0.5.0", - "@typechain/hardhat": "^9.0.0", - "@typechain/truffle-v5": "^8.0.6", - "@typechain/web3-v1": "^6.0.6", - "@types/chai": "^4.3.6", - "@types/mocha": "^10.0.2", - "@types/node": "^18.16.0", - "@typescript-eslint/eslint-plugin": "^6.13.1", - "@typescript-eslint/parser": "^6.13.1", - "chai": "^4.3.10", - "eslint": "^8.54.0", - "eslint-config-prettier": "^9.0.0", - "eslint-plugin-prettier": "^5.0.1", + "@typechain/ethers-v6": "^0.5.1", + "@typechain/hardhat": "^9.1.0", + "@typechain/truffle-v5": "^8.0.7", + "@typechain/web3-v1": "^6.0.7", + "@types/chai": "^4.3.16", + "@types/mocha": "^10.0.6", + "@types/node": "^18.19.33", + "@typescript-eslint/eslint-plugin": "^6.21.0", + "@typescript-eslint/parser": "^6.21.0", + "chai": "^4.4.1", + "eslint": "^8.57.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-prettier": "^5.1.3", "hardhat-contract-sizer": "^2.10.0", - "hardhat-gas-reporter": "^1.0.9", - "husky": "^8.0.2", - "mocha": "^10.2.0", - "prettier": "^3.1.0", + "hardhat-gas-reporter": "^1.0.10", + "husky": "^8.0.3", + "mocha": "^10.4.0", + "prettier": "^3.2.5", "prettier-plugin-solidity": "^1.3.1", - "solhint": "^4.0.0", + "solhint": "^4.5.4", "solhint-plugin-prettier": "^0.1.0", - "solidity-coverage": "^0.8.5", - "ts-node": "^10.9.1", + "solidity-coverage": "^0.8.12", + "ts-node": "^10.9.2", "tsconfig-paths": "^4.2.0", - "typescript": "^5.2.2", - "web3": "^1.7.5" + "typescript": "^5.4.5", + "web3": "^1.10.4" } } diff --git a/scripts/checkTokenBalance.ts b/scripts/checkTokenBalance.ts deleted file mode 100644 index 4bb22ed..0000000 --- a/scripts/checkTokenBalance.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { ethers } from 'hardhat'; - -import { ERC20 } from '@/generated-types/ethers'; - -async function main() { - // const user = '0x901F2d23823730fb7F2356920e0E273EFdCdFe17'; // me - const user = '0xb6067C1B07e3Fe12d18C11a0cc6F1366BD70EC95'; // token receiver - // const tokenAddress = '0xCF84E18F1a2803C15675622B24600910dc2a1E13'; // MOR - const tokenAddress = '0x87726993938107d9B9ce08c99BDde8736D899a5D'; // wStETH - const ERC20Factory = await ethers.getContractFactory('ERC20'); - const token = ERC20Factory.attach(tokenAddress) as ERC20; - - const balance = await token.balanceOf(user); - - console.log(`token ${tokenAddress} balance of ${user}: ${balance}`); -} - -main().catch((error) => { - console.error(error); - process.exitCode = 1; -}); - -// npx hardhat run scripts/checkTokenBalance.ts --network arbitrum_goerli diff --git a/scripts/getOverplus.ts b/scripts/getOverplus.ts deleted file mode 100644 index 7bb3979..0000000 --- a/scripts/getOverplus.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { ethers } from 'hardhat'; - -import { IDistribution } from '@/generated-types/ethers'; - -async function main() { - const distributionFactory = await ethers.getContractFactory('Distribution', { - libraries: { - LinearDistributionIntervalDecrease: '0x7431aDa8a591C955a994a21710752EF9b882b8e3', - }, - }); - - const distribution = distributionFactory.attach('0x47176B2Af9885dC6C4575d4eFd63895f7Aaa4790') as IDistribution; - - console.log(await distribution.overplus()); - - console.log(')'); -} - -main().catch((error) => { - console.error(error); - process.exitCode = 1; -}); - -// npx hardhat run scripts/getOverplus.ts --network localhost diff --git a/scripts/retryPayload.ts b/scripts/retryPayload.ts deleted file mode 100644 index fcbee1c..0000000 --- a/scripts/retryPayload.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { ethers } from 'hardhat'; - -import { ILayerZeroEndpoint } from '@/generated-types/ethers/@layerzerolabs/solidity-examples/contracts/lzApp/interfaces'; - -async function getNonce() { - const l2MessageReceiver = await ethers.getContractAt( - 'L2MessageReceiver', - '0xc37ff39e5a50543ad01e42c4cd88c2939dd13002', - (await ethers.getSigners())[0], - ); - - console.log(await l2MessageReceiver.nonce()); -} - -async function main() { - await getNonce(); - const lzEndpoint = (await ethers.getContractAt( - '@layerzerolabs/lz-evm-sdk-v1-0.7/contracts/interfaces/ILayerZeroEndpoint.sol:ILayerZeroEndpoint', - '0x6098e96a28E02f27B1e6BD381f870F1C8Bd169d3', - (await ethers.getSigners())[0], - )) as unknown as ILayerZeroEndpoint; - - const remoteAndLocal = ethers.solidityPacked( - ['address', 'address'], - ['0xeec0df0991458274ff0ede917e9827ffc67a8332', '0xc37ff39e5a50543ad01e42c4cd88c2939dd13002'], - ); - const tx = await lzEndpoint.retryPayload( - '10161', - remoteAndLocal, - '0x000000000000000000000000901f2d23823730fb7f2356920e0e273efdcdfe1700000000000000000000000000000000000000000000000322994640a6175555', - ); - - console.log(tx); -} - -main().catch((error) => { - console.error(error); - process.exitCode = 1; -}); - -// npx hardhat run scripts/retryPayload.ts --network arbitrum_sepolia diff --git a/scripts/sendMOROFTToAnotherChain.ts b/scripts/sendMOROFTToAnotherChain.ts deleted file mode 100644 index 1f0bc8c..0000000 --- a/scripts/sendMOROFTToAnotherChain.ts +++ /dev/null @@ -1,150 +0,0 @@ -import { HardhatEthersSigner } from '@nomicfoundation/hardhat-ethers/signers'; -import { ethers } from 'hardhat'; - -import { wei } from './utils/utils'; - -import { MOROFT } from '@/generated-types/ethers'; - -type NetworkConfig = { - moroftAddress: string; - moroftContract?: MOROFT; - chainId: string; -}; - -const arbitrumSepolia: NetworkConfig = { - moroftAddress: '0x51f970885e90DA7dc9E3Fee37A115aD04e94F5CE', - chainId: '40231', -}; - -const sepolia: NetworkConfig = { - moroftAddress: '0xAE5227BEfEE7292Ef1a5C2376629b32d65bB29be', - chainId: '40161', -}; - -const mumbai: NetworkConfig = { - moroftAddress: '0xACabF3283a8083868a817C24f17C3c3e2D3339B0', - chainId: '40109', -}; - -const sender = '0x19ec1E4b714990620edf41fE28e9a1552953a7F4'; -const receiver = '0x19ec1E4b714990620edf41fE28e9a1552953a7F4'; - -let tx; - -const sendTokens = async ( - signer: HardhatEthersSigner, - amount: bigint, - receiver: string, - fromConfig: NetworkConfig, - toConfig: NetworkConfig, -) => { - if (!fromConfig.moroftContract) { - console.log('Failed to attach contract.'); - - return; - } - - const receiverBytes32Address = ethers.zeroPadValue(receiver, 32); - - const sendParams = { - dstEid: toConfig.chainId, - to: receiverBytes32Address, - amountLD: amount, - minAmountLD: amount, - extraOptions: '0x', - composeMsg: '0x', - oftCmd: '0x', - }; - - const quoteRes = await fromConfig.moroftContract.quoteSend(sendParams, false); - - const messagingFee = { - nativeFee: quoteRes[0].toString(), - lzTokenFee: 0, - }; - - tx = await fromConfig.moroftContract.connect(signer).send(sendParams, messagingFee, sender, { - value: messagingFee.nativeFee, - }); - await tx.wait(); - - console.log('Success: ', tx.hash); -}; - -const configureBridge = async (signer: HardhatEthersSigner, fromConfig: NetworkConfig, toConfig: NetworkConfig) => { - if (!fromConfig.moroftContract) { - console.log('Failed to attach contract.'); - - return; - } - - // Add to contract as allowed peer - tx = await fromConfig.moroftContract - .connect(signer) - .setPeer(toConfig.chainId, ethers.zeroPadValue(toConfig.moroftAddress, 32)); - await tx.wait(); - console.log( - 'Is peer has set:', - await fromConfig.moroftContract.isPeer(toConfig.chainId, ethers.zeroPadValue(toConfig.moroftAddress, 32)), - ); - - // // !!!!! Use it only for forming `options`. !!!!! - // const OptionsGenerator = await ethers.getContractFactory('OptionsGenerator'); - // const optionsGenerator = await OptionsGenerator.deploy(); - - // // Detect options for enforce params - // const executorGas = 60000; // Gas limit for the executor - // const executorValue = 0; // msg.value for the lzReceive() function on destination in wei - // // https://docs.layerzero.network/v2/developers/evm/gas-settings/options - // const options = await optionsGenerator.createLzReceiveOption(executorGas, executorValue); - // console.log('Options for next step: ', options); - - // https://docs.layerzero.network/v2/developers/evm/oapp/overview#message-execution-options - const enforcedOptionParam = [ - { - eid: toConfig.chainId, - msgType: 1, - options: '0x0003010011010000000000000000000000000000ea60', - }, - ]; - - tx = await fromConfig.moroftContract.connect(signer).setEnforcedOptions(enforcedOptionParam); - await tx.wait(); -}; - -async function main() { - // Use `getImpersonatedSigner` for testing on fork - // const signer = await ethers.getImpersonatedSigner('0x19ec1E4b714990620edf41fE28e9a1552953a7F4'); - const [signer] = await ethers.getSigners(); - - const MOROFT = await ethers.getContractFactory('MOROFT'); - - arbitrumSepolia.moroftContract = MOROFT.attach(arbitrumSepolia.moroftAddress) as MOROFT; - sepolia.moroftContract = MOROFT.attach(sepolia.moroftAddress) as MOROFT; - mumbai.moroftContract = MOROFT.attach(mumbai.moroftAddress) as MOROFT; - - // **** STEP #1 **** - // Configure from `mumbai` to `sepolia`. Call it from `mumbai` - // await configureBridge(signer, mumbai, sepolia); - - // Configure from `sepolia` to `mumbai`. Call it from `sepolia` - // await configureBridge(signer, sepolia, mumbai); - // END - - // **** STEP #2 **** - // Send tokens from `mumbai` to `sepolia`. Call it from `mumbai` - // await sendTokens(signer, wei(100), receiver, mumbai, sepolia); - - // Send tokens from `sepolia` to `mumbai`. Call it from `sepolia` - await sendTokens(signer, wei(50), receiver, sepolia, mumbai); - // END -} - -main().catch((error) => { - console.error(error); - process.exitCode = 1; -}); - -// npx hardhat run scripts/sendMOROFTToAnotherChain.ts -// npx hardhat run scripts/sendMOROFTToAnotherChain.ts --network mumbai -// npx hardhat run scripts/sendMOROFTToAnotherChain.ts --network sepolia diff --git a/scripts/tryAddLiquidity.ts b/scripts/tryAddLiquidity.ts deleted file mode 100644 index 69a17a8..0000000 --- a/scripts/tryAddLiquidity.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { ethers } from 'hardhat'; - -import { ERC20, L2TokenReceiver } from '@/generated-types/ethers'; - -const l2MessageReceiverAddress = '0x2C0f43E5C92459F62C102517956A95E88E177e95'; -const wstethAddress = '0x6320cD32aA674d2898A68ec82e869385Fc5f7E2f'; -const morAddress = '0x454AE850eE61a98BF16FABA3a73fb0dD02D75C40'; - -async function main() { - const L2TokenReceiver = await ethers.getContractFactory('L2TokenReceiver'); - const l2TokenReceiver = L2TokenReceiver.attach(l2MessageReceiverAddress) as L2TokenReceiver; - - const ERC20Factory = await ethers.getContractFactory('ERC20'); - const wsteth = ERC20Factory.attach(wstethAddress) as ERC20; - const mor = ERC20Factory.attach(morAddress) as ERC20; - - const wstethBalance = await wsteth.balanceOf(l2MessageReceiverAddress); - const morBalance = await mor.balanceOf(l2MessageReceiverAddress); - - console.log(`wsteth balance of ${l2MessageReceiverAddress}: ${wstethBalance}`); - console.log(`mor balance of ${l2MessageReceiverAddress}: ${morBalance}`); - - const tx = await l2TokenReceiver.increaseLiquidityCurrentRange(86416, wstethBalance, morBalance, 0, 0); - await tx.wait(); - - console.log('liquidity added'); - - const wstethBalanceAfter = await wsteth.balanceOf(l2MessageReceiverAddress); - const morBalanceAfter = await mor.balanceOf(l2MessageReceiverAddress); - - console.log(`wsteth balance of ${l2MessageReceiverAddress}: ${wstethBalanceAfter}`); - console.log(`mor balance of ${l2MessageReceiverAddress}: ${morBalanceAfter}`); -} - -main().catch((error) => { - console.error(error); - process.exitCode = 1; -}); - -// npx hardhat run scripts/tryAddLiquidity.ts --network arbitrum_goerli diff --git a/scripts/tryTokensSwap.ts b/scripts/tryTokensSwap.ts deleted file mode 100644 index ca822b4..0000000 --- a/scripts/tryTokensSwap.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { ethers } from 'hardhat'; - -import { L2TokenReceiver } from '@/generated-types/ethers'; - -async function main() { - const L2TokenReceiver = await ethers.getContractFactory('L2TokenReceiver'); - const l2TokenReceiver = L2TokenReceiver.attach('0x2C0f43E5C92459F62C102517956A95E88E177e95') as L2TokenReceiver; - - const tx = await l2TokenReceiver.swap(500, 0); - console.log(tx); -} - -main().catch((error) => { - console.error(error); - process.exitCode = 1; -}); - -// npx hardhat run scripts/tryTokensSwap.ts --network arbitrum_goerli diff --git a/test/Factory.test.ts b/test/Factory.test.ts new file mode 100644 index 0000000..33a5c77 --- /dev/null +++ b/test/Factory.test.ts @@ -0,0 +1,153 @@ +import { SignerWithAddress } from '@nomicfoundation/hardhat-ethers/signers'; +import { expect } from 'chai'; +import { ethers } from 'hardhat'; + +import { Reverter } from './helpers/reverter'; + +import { FactoryMock, FactoryMockV2 } from '@/generated-types/ethers'; +import { ZERO_ADDR } from '@/scripts/utils/constants'; + +describe('Factory', () => { + const reverter = new Reverter(); + + let OWNER: SignerWithAddress; + let SECOND: SignerWithAddress; + + let factory: FactoryMock; + + before(async () => { + [OWNER, SECOND] = await ethers.getSigners(); + + const [FactoryMockFactory, ERC1967ProxyFactory] = await Promise.all([ + ethers.getContractFactory('FactoryMock'), + ethers.getContractFactory('ERC1967Proxy'), + ]); + + const factoryImpl = await FactoryMockFactory.deploy(); + const factoryProxy = await ERC1967ProxyFactory.deploy(factoryImpl, '0x'); + factory = FactoryMockFactory.attach(factoryProxy) as FactoryMock; + + await factory.Factory_init(); + + await reverter.snapshot(); + }); + + afterEach(reverter.revert); + + describe('UUPS proxy functionality', () => { + describe('#Factory_init', () => { + it('should revert if try to call init function twice', async () => { + const reason = 'Initializable: contract is already initialized'; + + await expect(factory.Factory_init()).to.be.rejectedWith(reason); + }); + it('should revert if call init function incorrect', async () => { + const reason = 'Initializable: contract is not initializing'; + + await expect(factory.mockInit()).to.be.rejectedWith(reason); + }); + }); + + describe('#_authorizeUpgrade', () => { + it('should correctly upgrade', async () => { + const factoryV2Factory = await ethers.getContractFactory('FactoryMockV2'); + const factoryV2Implementation = await factoryV2Factory.deploy(); + + await factory.upgradeTo(factoryV2Implementation); + + const factoryV2 = factoryV2Factory.attach(await factory.getAddress()) as FactoryMockV2; + + expect(await factoryV2.version()).to.eq(2); + }); + it('should revert if caller is not the owner', async () => { + await expect(factory.connect(SECOND).upgradeTo(ZERO_ADDR)).to.be.revertedWith( + 'Ownable: caller is not the owner', + ); + }); + }); + }); + + describe('pause', () => { + it('should pause', async () => { + expect(await factory.paused()).to.be.false; + await factory.pause(); + expect(await factory.paused()).to.be.true; + }); + it('should revert if called by non-owner', async () => { + await expect(factory.connect(SECOND).pause()).to.be.revertedWith('Ownable: caller is not the owner'); + }); + }); + + describe('unpause', () => { + it('should unpause', async () => { + await factory.pause(); + expect(await factory.paused()).to.be.true; + + await factory.unpause(); + expect(await factory.paused()).to.be.false; + }); + it('should revert if called by non-owner', async () => { + await factory.pause(); + await expect(factory.connect(SECOND).unpause()).to.be.revertedWith('Ownable: caller is not the owner'); + }); + }); + + describe('setImplementation', () => { + it('should set implementation', async () => { + await factory.setImplementation(0, SECOND); + + expect(await factory.getImplementation(0)).to.eq(SECOND); + + await factory.setImplementation(0, ZERO_ADDR); + + expect(await factory.getImplementation(0)).to.eq(ZERO_ADDR); + }); + it('should revert if called by non-owner', async () => { + await expect(factory.connect(SECOND).setImplementation(0, SECOND)).to.be.revertedWith( + 'Ownable: caller is not the owner', + ); + }); + }); + + describe('getImplementation', () => { + it('should get implementation', async () => { + expect(await factory.getImplementation(0)).to.eq(ZERO_ADDR); + + await factory.setImplementation(0, SECOND); + + expect(await factory.getImplementation(0)).to.eq(SECOND.address); + }); + }); + + describe('deploy2', () => { + beforeEach(async () => { + const L1SenderFactory = await ethers.getContractFactory('L1Sender'); + const L1SenderImplementation = await L1SenderFactory.deploy(); + + await factory.setImplementation(0, L1SenderImplementation); + }); + + it('should deploy contract', async () => { + const proxy = await factory.deploy2.staticCall(0, 'name'); + await factory.deploy2(0, 'name'); + + expect(await factory.deployedProxies(OWNER, 'name', 0)).to.eq(proxy); + }); + it('should deploy the same name for different addresses', async () => { + await factory.deploy2(0, 'name'); + + await factory.connect(SECOND).deploy2(0, 'name'); + }); + it('should revert if name is an empty string', async () => { + await expect(factory.deploy2(0, '')).to.be.revertedWith('F: poolName_ is empty'); + }); + it('should revert if implementation is not set', async () => { + await expect(factory.deploy2(1, 'name')).to.be.revertedWith('F: implementation not found'); + }); + it('should revert if called twice with the same name for same address', async () => { + await factory.deploy2(0, 'name'); + + await expect(factory.deploy2(0, 'name')).to.be.revertedWith('F: salt used'); + }); + }); +}); diff --git a/test/Integration.test.ts b/test/Integration.test.ts new file mode 100644 index 0000000..2305922 --- /dev/null +++ b/test/Integration.test.ts @@ -0,0 +1,276 @@ +import { SignerWithAddress } from '@nomicfoundation/hardhat-ethers/signers'; +import { expect } from 'chai'; +import { ethers } from 'hardhat'; + +import { setNextTime } from './helpers/block-helper'; +import { getDefaultPool, oneDay } from './helpers/distribution-helper'; +import { PoolTypesL1, PoolTypesL2 } from './helpers/helper'; +import { Reverter } from './helpers/reverter'; + +import { + FeeConfig, + IL1Factory, + IL2Factory, + L1Factory, + L2Factory, + LZEndpointMock, + WETHMock, + WStETHMock, +} from '@/generated-types/ethers'; +import { ZERO_ADDR } from '@/scripts/utils/constants'; +import { wei } from '@/scripts/utils/utils'; + +describe('Integration', () => { + const senderChainId = 101; + const receiverChainId = 102; + + const reverter = new Reverter(); + + let OWNER: SignerWithAddress; + + let l1Factory: L1Factory; + let l2Factory: L2Factory; + + let l2Weth: WETHMock; + let l1WstEth: WStETHMock; + let l2WstEth: WStETHMock; + + let l1LzEndpoint: LZEndpointMock; + let l2LzEndpoint: LZEndpointMock; + + let depositTokenExternalDeps: IL1Factory.DepositTokenExternalDepsStruct; + let lzExternalDeps: IL1Factory.LzExternalDepsStruct; + let arbExternalDeps: IL1Factory.ArbExternalDepsStruct; + + let lzTokenExternalDeps: IL2Factory.LzExternalDepsStruct; + let uniswapExternalDeps: IL2Factory.UniswapExternalDepsStruct; + + before(async () => { + [OWNER] = await ethers.getSigners(); + + const [ + L1Factory, + ERC1967ProxyFactory, + + LinearDistributionIntervalDecreaseFactory, + L1SenderFactory, + StEthMockFactory, + WstEthMockFactory, + GatewayRouterMockFactory, + FeeConfigFactory, + LZEndpointMockFactory, + + LayerZeroEndpointV2Mock, + MOR20DeployerFactory, + SwapRouterMockFactory, + NonfungiblePositionManagerMockFactory, + L2MessageReceiverFactory, + L2TokenReceiverFactory, + WETHMockFactory, + ] = await Promise.all([ + ethers.getContractFactory('L1Factory'), + ethers.getContractFactory('ERC1967Proxy'), + + ethers.getContractFactory('LinearDistributionIntervalDecrease'), + ethers.getContractFactory('L1Sender'), + ethers.getContractFactory('StETHMock'), + ethers.getContractFactory('WStETHMock'), + ethers.getContractFactory('GatewayRouterMock'), + ethers.getContractFactory('FeeConfig'), + ethers.getContractFactory('LZEndpointMock'), + + ethers.getContractFactory('LayerZeroEndpointV2Mock'), + ethers.getContractFactory('MOR20Deployer'), + ethers.getContractFactory('SwapRouterMock'), + ethers.getContractFactory('NonfungiblePositionManagerMock'), + ethers.getContractFactory('L2MessageReceiver'), + ethers.getContractFactory('L2TokenReceiver'), + ethers.getContractFactory('WETHMock'), + ]); + + const [ + linearDistributionIntervalDecrease, + l1SenderImplementation, + l1StEth, + l2StEth, + gatewayRouterMock, + feeConfigImplementation, + l1LzEndpointMock, + + l2LzEndpointMock, + l2LzEndpointOFT, + l2MessageReceiverImplementation, + l2TokenReceiverImplementation, + MOR20Deployer, + swapRouter, + nonfungiblePositionManager, + l2WethMock, + ] = await Promise.all([ + LinearDistributionIntervalDecreaseFactory.deploy(), + L1SenderFactory.deploy(), + StEthMockFactory.deploy(), + StEthMockFactory.deploy(), + GatewayRouterMockFactory.deploy(), + FeeConfigFactory.deploy(), + LZEndpointMockFactory.deploy(senderChainId), + + LZEndpointMockFactory.deploy(receiverChainId), + LayerZeroEndpointV2Mock.deploy(receiverChainId, OWNER), + L2MessageReceiverFactory.deploy(), + L2TokenReceiverFactory.deploy(), + MOR20DeployerFactory.deploy(), + SwapRouterMockFactory.deploy(), + NonfungiblePositionManagerMockFactory.deploy(), + WETHMockFactory.deploy(), + ]); + l2Weth = l2WethMock; + l1LzEndpoint = l1LzEndpointMock; + l2LzEndpoint = l2LzEndpointMock; + + const distributionFactory = await ethers.getContractFactory('Distribution', { + libraries: { + LinearDistributionIntervalDecrease: linearDistributionIntervalDecrease, + }, + }); + const distributionImplementation = await distributionFactory.deploy(); + + l1WstEth = await WstEthMockFactory.deploy(l1StEth); + l2WstEth = await WstEthMockFactory.deploy(l2StEth); + + const l1FactoryImpl = await L1Factory.deploy(); + const l1FactoryProxy = await ERC1967ProxyFactory.deploy(l1FactoryImpl, '0x'); + l1Factory = L1Factory.attach(l1FactoryProxy) as L1Factory; + await l1Factory.L1Factory_init(); + + await l1Factory.setImplementation(PoolTypesL1.DISTRIBUTION, distributionImplementation); + await l1Factory.setImplementation(PoolTypesL1.L1_SENDER, l1SenderImplementation); + + const feeConfigProxy = await ERC1967ProxyFactory.deploy(feeConfigImplementation, '0x'); + const feeConfig = FeeConfigFactory.attach(feeConfigProxy) as FeeConfig; + await feeConfig.__FeeConfig_init(OWNER, wei(0.1, 25)); + + const L2Factory = await ethers.getContractFactory('L2Factory', { + libraries: { + MOR20Deployer: MOR20Deployer, + }, + }); + const l2FactoryImpl = await L2Factory.deploy(); + const l2FactoryProxy = await ERC1967ProxyFactory.deploy(l2FactoryImpl, '0x'); + l2Factory = L2Factory.attach(l2FactoryProxy) as L2Factory; + await l2Factory.L2Factory_init(); + + await l2Factory.setImplementation(PoolTypesL2.L2_MESSAGE_RECEIVER, l2MessageReceiverImplementation); + await l2Factory.setImplementation(PoolTypesL2.L2_TOKEN_RECEIVER, l2TokenReceiverImplementation); + + depositTokenExternalDeps = { + token: l1StEth, + wToken: l1WstEth, + }; + lzExternalDeps = { + endpoint: l1LzEndpoint, + zroPaymentAddress: ZERO_ADDR, + adapterParams: '0x', + destinationChainId: receiverChainId, + }; + arbExternalDeps = { + endpoint: gatewayRouterMock, + }; + + await l1Factory.setDepositTokenExternalDeps(depositTokenExternalDeps); + await l1Factory.setLzExternalDeps(lzExternalDeps); + await l1Factory.setArbExternalDeps(arbExternalDeps); + await l1Factory.setFeeConfig(feeConfig); + + lzTokenExternalDeps = { + endpoint: l2LzEndpoint, + oftEndpoint: l2LzEndpointOFT, + senderChainId: senderChainId, + }; + uniswapExternalDeps = { + router: swapRouter, + nonfungiblePositionManager: nonfungiblePositionManager, + }; + await l2Factory.setLzExternalDeps(lzTokenExternalDeps); + await l2Factory.setUniswapExternalDeps(uniswapExternalDeps); + + await l1StEth.mint(OWNER, wei(1000)); + + await reverter.snapshot(); + }); + + afterEach(reverter.revert); + + describe('Should deploy L1 and L2', () => { + const protocolName = 'Mor20'; + + it('should deploy correctly', async () => { + const [l2MessageReceiverPredicted, l2TokenReceiverPredicted] = await l2Factory.predictAddresses( + protocolName, + OWNER, + ); + + const [distributionPredicted, l1SenderPredicted] = await l1Factory.predictAddresses(protocolName, OWNER); + + await l1LzEndpoint.setDestLzEndpoint(l2MessageReceiverPredicted, l2LzEndpoint); + + const l1Params: IL1Factory.L1ParamsStruct = { + protocolName: protocolName, + isNotUpgradeable: true, + poolsInfo: [], + l2TokenReceiver: l2TokenReceiverPredicted, + l2MessageReceiver: l2MessageReceiverPredicted, + }; + const l2Params: IL2Factory.L2ParamsStruct = { + protocolName: protocolName, + mor20Name: 'MOR20', + mor20Symbol: 'M20', + l1Sender: l1SenderPredicted, + firstSwapParams_: { + tokenIn: l2WstEth, + tokenOut: l2Weth, + fee: 100, + }, + secondSwapFee: 3000, + }; + + await l1Factory.deploy(l1Params); + await l2Factory.deploy(l2Params); + + const distribution = await ethers.getContractAt('Distribution', distributionPredicted); + const MOR20 = await ethers.getContractAt( + 'MOR20', + await l2Factory.deployedProxies(OWNER, protocolName, PoolTypesL2.MOR20), + ); + + const pool = getDefaultPool(); + await distribution.createPool(pool); + + const l1StEth = await ethers.getContractAt('StETHMock', (await l1Factory.depositTokenExternalDeps()).token); + await l1StEth.approve(distribution, wei(1)); + await distribution.stake(0, wei(1)); + + await l1StEth.setTotalPooledEther((await l1StEth.totalPooledEther()) * 2n); + + const overplus = await distribution.overplus(); + expect(overplus).to.be.eq(wei(1)); + + let tx = await distribution.bridgeOverplus(1, 1, 1); + await expect(tx).to.changeTokenBalances(l1StEth, [distributionPredicted, OWNER], [wei(-1), wei(0.1)]); + // we made an assumption that the token address is l1WstEth + await expect(tx).to.changeTokenBalance(l1WstEth, l2TokenReceiverPredicted, wei(0.9)); + expect(await distribution.overplus()).to.be.eq(0); + + await setNextTime(2 * oneDay); + + tx = await distribution.withdraw(0, wei(1)); + await expect(tx).to.changeTokenBalances(l1StEth, [distributionPredicted, OWNER], [wei(-1), wei(1)]); + + const reward = await distribution.getCurrentUserReward(0, OWNER); + expect(reward).to.equal(wei(100)); + + await distribution.claim(0, OWNER, { value: wei(0.1) }); + + expect(await MOR20.balanceOf(OWNER)).to.be.eq(reward); + }); + }); +}); diff --git a/test/L1/Distribution.test.ts b/test/L1/Distribution.test.ts index 8df6c9c..a612186 100644 --- a/test/L1/Distribution.test.ts +++ b/test/L1/Distribution.test.ts @@ -6,19 +6,22 @@ import { Distribution, DistributionV2, Distribution__factory, + FeeConfig, GatewayRouterMock, IDistribution, IL1Sender, L1Sender, L2MessageReceiver, - L2TokenReceiverV2, LZEndpointMock, + LayerZeroEndpointV2Mock, LinearDistributionIntervalDecrease, + MOR20, NonfungiblePositionManagerMock, StETHMock, SwapRouterMock, WStETHMock, } from '@/generated-types/ethers'; +import { L2TokenReceiver } from '@/generated-types/ethers/contracts/L2/L2TokenReceiver'; import { ZERO_ADDR } from '@/scripts/utils/constants'; import { wei } from '@/scripts/utils/utils'; import { getCurrentBlockTime, setNextTime, setTime } from '@/test/helpers/block-helper'; @@ -39,16 +42,18 @@ describe('Distribution', () => { let lib: LinearDistributionIntervalDecrease; - let rewardToken: MOR; + let rewardToken: MOR20; let depositToken: StETHMock; let wstETH: WStETHMock; let lZEndpointMockSender: LZEndpointMock; let lZEndpointMockReceiver: LZEndpointMock; + let lZEndpointMockOFT: LayerZeroEndpointV2Mock; + let feeConfig: FeeConfig; let l1Sender: L1Sender; let l2MessageReceiver: L2MessageReceiver; - let l2TokenReceiver: L2TokenReceiverV2; + let l2TokenReceiver: L2TokenReceiver; before(async () => { await setTime(oneHour); @@ -67,27 +72,32 @@ describe('Distribution', () => { gatewayRouterMock, SwapRouterMock, NonfungiblePositionManagerMock, + LZEndpointMockOFT, + feeConfigFactory, ] = await Promise.all([ ethers.getContractFactory('LinearDistributionIntervalDecrease'), ethers.getContractFactory('ERC1967Proxy'), - ethers.getContractFactory('MOR'), + ethers.getContractFactory('MOR20'), ethers.getContractFactory('StETHMock'), ethers.getContractFactory('WStETHMock'), ethers.getContractFactory('L1Sender'), ethers.getContractFactory('LZEndpointMock'), ethers.getContractFactory('L2MessageReceiver'), - ethers.getContractFactory('L2TokenReceiverV2'), + ethers.getContractFactory('L2TokenReceiver'), ethers.getContractFactory('GatewayRouterMock'), ethers.getContractFactory('SwapRouterMock'), ethers.getContractFactory('NonfungiblePositionManagerMock'), + ethers.getContractFactory('LayerZeroEndpointV2Mock'), + ethers.getContractFactory('FeeConfig'), ]); let gatewayRouter: GatewayRouterMock; let swapRouter: SwapRouterMock; let nonfungiblePositionManager: NonfungiblePositionManagerMock; - let l2TokenReceiverImplementation: L2TokenReceiverV2; + let l2TokenReceiverImplementation: L2TokenReceiver; let l2MessageReceiverImplementation: L2MessageReceiver; let l1SenderImplementation: L1Sender; + let feeConfigImplementation: FeeConfig; // START deploy contracts without deps [ lib, @@ -100,6 +110,8 @@ describe('Distribution', () => { l2TokenReceiverImplementation, l2MessageReceiverImplementation, l1SenderImplementation, + lZEndpointMockOFT, + feeConfigImplementation, ] = await Promise.all([ libFactory.deploy(), stETHMockFactory.deploy(), @@ -111,6 +123,8 @@ describe('Distribution', () => { L2TokenReceiver.deploy(), L2MessageReceiver.deploy(), l1SenderFactory.deploy(), + LZEndpointMockOFT.deploy(receiverChainId, OWNER), + feeConfigFactory.deploy(), ]); distributionFactory = await ethers.getContractFactory('Distribution', { @@ -121,20 +135,33 @@ describe('Distribution', () => { const distributionImplementation = await distributionFactory.deploy(); // END + lZEndpointMockOFT = await LZEndpointMockOFT.deploy(receiverChainId, OWNER); + wstETH = await wstETHMockFactory.deploy(depositToken); + const feeConfigProxy = await ERC1967ProxyFactory.deploy(feeConfigImplementation, '0x'); + feeConfig = feeConfigFactory.attach(feeConfigProxy) as FeeConfig; + await feeConfig.__FeeConfig_init(OWNER, wei(0.1, 25)); + const l2MessageReceiverProxy = await ERC1967ProxyFactory.deploy(l2MessageReceiverImplementation, '0x'); l2MessageReceiver = L2MessageReceiver.attach(l2MessageReceiverProxy) as L2MessageReceiver; - await l2MessageReceiver.L2MessageReceiver__init(); const l2TokenReceiverProxy = await ERC1967ProxyFactory.deploy(l2TokenReceiverImplementation, '0x'); - l2TokenReceiver = L2TokenReceiver.attach(l2TokenReceiverProxy) as L2TokenReceiverV2; - await l2TokenReceiver.L2TokenReceiver__init(swapRouter, nonfungiblePositionManager, { - tokenIn: depositToken, - tokenOut: depositToken, - fee: 3000, - sqrtPriceLimitX96: 0, - }); + l2TokenReceiver = L2TokenReceiver.attach(l2TokenReceiverProxy) as L2TokenReceiver; + await l2TokenReceiver.L2TokenReceiver__init( + swapRouter, + nonfungiblePositionManager, + { + tokenIn: depositToken, + tokenOut: depositToken, + fee: 3000, + }, + { + tokenIn: depositToken, + tokenOut: depositToken, + fee: 3000, + }, + ); // START deploy distribution contract const distributionProxy = await ERC1967ProxyFactory.deploy(await distributionImplementation.getAddress(), '0x'); @@ -159,10 +186,10 @@ describe('Distribution', () => { await l1Sender.L1Sender__init(distribution, rewardTokenConfig, depositTokenConfig); // Deploy reward token - rewardToken = await MORFactory.deploy(wei(1000000000)); + rewardToken = await MORFactory.deploy('MOR', 'MOR', lZEndpointMockOFT, OWNER, l2MessageReceiver); await rewardToken.transferOwnership(l2MessageReceiver); - await l2MessageReceiver.setParams(rewardToken, { + await l2MessageReceiver.L2MessageReceiver__init(rewardToken, { gateway: lZEndpointMockReceiver, sender: l1Sender, senderChainId: senderChainId, @@ -170,7 +197,7 @@ describe('Distribution', () => { await lZEndpointMockSender.setDestLzEndpoint(l2MessageReceiver, lZEndpointMockReceiver); - await distribution.Distribution_init(depositToken, l1Sender, []); + await distribution.Distribution_init(depositToken, l1Sender, feeConfig, []); await Promise.all([depositToken.mint(OWNER.address, wei(1000)), depositToken.mint(SECOND.address, wei(1000))]); await Promise.all([ @@ -191,7 +218,7 @@ describe('Distribution', () => { const distribution = await distributionFactory.deploy(); - await expect(distribution.Distribution_init(depositToken, l1Sender, [])).to.be.revertedWith(reason); + await expect(distribution.Distribution_init(depositToken, l1Sender, feeConfig, [])).to.be.revertedWith(reason); }); }); @@ -216,7 +243,7 @@ describe('Distribution', () => { const distribution = distributionFactory.attach(await distributionProxy.getAddress()) as Distribution; - await distribution.Distribution_init(depositToken, l1Sender, [pool1, pool2]); + await distribution.Distribution_init(depositToken, l1Sender, feeConfig, [pool1, pool2]); const pool1Data: IDistribution.PoolStruct = await distribution.pools(0); expect(_comparePoolStructs(pool1, pool1Data)).to.be.true; @@ -227,7 +254,7 @@ describe('Distribution', () => { it('should revert if try to call init function twice', async () => { const reason = 'Initializable: contract is already initialized'; - await expect(distribution.Distribution_init(depositToken, l1Sender, [])).to.be.rejectedWith(reason); + await expect(distribution.Distribution_init(depositToken, l1Sender, feeConfig, [])).to.be.rejectedWith(reason); }); }); @@ -314,9 +341,7 @@ describe('Distribution', () => { it('should edit pool with correct data', async () => { const newPool = { ...defaultPool, - payoutStart: 10 * oneDay, decreaseInterval: 10 * oneDay, - withdrawLockPeriod: 10 * oneDay, initialReward: wei(111), rewardDecrease: wei(222), minimalStake: wei(333), @@ -343,6 +368,21 @@ describe('Distribution', () => { await expect(distribution.editPool(poolId, newPool)).to.be.rejectedWith('DS: invalid decrease interval'); }); + it('if `payoutStart changes`', async () => { + const newPool = { ...defaultPool, payoutStart: oneDay * 2 }; + + await expect(distribution.editPool(poolId, newPool)).to.be.rejectedWith('DS: invalid payout start value'); + }); + it('if `withdrawLockPeriod` changes', async () => { + const newPool = { ...defaultPool, withdrawLockPeriod: oneDay * 2 }; + + await expect(distribution.editPool(poolId, newPool)).to.be.rejectedWith('DS: invalid WLP value'); + }); + it('if `withdrawLockPeriodAfterStake` changes', async () => { + const newPool = { ...defaultPool, withdrawLockPeriodAfterStake: oneDay * 2 }; + + await expect(distribution.editPool(poolId, newPool)).to.be.rejectedWith('DS: invalid WLPAS value'); + }); }); it('should revert if caller is not owner', async () => { @@ -1098,27 +1138,6 @@ describe('Distribution', () => { expect(userData.deposited).to.eq(wei(1)); expect(userData.pendingRewards).to.eq(0); }); - it('should not save reward to pending reward if cannot mint reward token', async () => { - const amountToMintMaximum = (await rewardToken.cap()) - (await rewardToken.totalSupply()); - - await _getRewardTokenFromPool(distribution, amountToMintMaximum - wei(1), OWNER); - - await distribution.stake(poolId, wei(10)); - - await setNextTime(oneDay + oneDay); - - let tx = await distribution.claim(poolId, OWNER, { value: wei(0.5) }); - await expect(tx).to.changeTokenBalance(rewardToken, OWNER, wei(100)); - let userData = await distribution.usersData(OWNER, poolId); - expect(userData.pendingRewards).to.equal(wei(0)); - - await setNextTime(oneDay + oneDay * 2); - - tx = await distribution.claim(poolId, OWNER, { value: wei(0.5) }); - await expect(tx).to.changeTokenBalance(rewardToken, OWNER, wei(98)); - userData = await distribution.usersData(OWNER, poolId); - expect(userData.pendingRewards).to.equal(wei(0)); - }); it("should revert if pool doesn't exist", async () => { await expect(distribution.connect(SECOND).claim(1, SECOND)).to.be.revertedWith("DS: pool doesn't exist"); }); @@ -1643,7 +1662,51 @@ describe('Distribution', () => { await distribution.createPool(pool); }); - it('should bridge overplus', async () => { + it('should bridge overplus with basic fee', async () => { + const l2TokenReceiverAddress = await l2TokenReceiver.getAddress(); + + await distribution.stake(1, wei(1)); + + await depositToken.setTotalPooledEther((await depositToken.totalPooledEther()) * 2n); + + let overplus = await distribution.overplus(); + expect(overplus).to.eq(wei(1)); + + const fee = (overplus * (await feeConfig.baseFee())) / wei(1, 25); + overplus -= fee; + + const bridgeMessageId = await distribution.bridgeOverplus.staticCall(1, 1, 1); + const tx = await distribution.bridgeOverplus(1, 1, 1); + await expect(tx).to.emit(distribution, 'OverplusBridged').withArgs(overplus, bridgeMessageId); + await expect(tx).to.changeTokenBalance(depositToken, distribution, wei(-1)); + expect(await wstETH.balanceOf(l2TokenReceiverAddress)).to.eq(overplus); + await expect(tx).to.changeTokenBalance(depositToken, OWNER, fee); + }); + it('should bridge overplus with custom fee', async () => { + await feeConfig.setFee(distribution, wei(0.5, 25)); + + const l2TokenReceiverAddress = await l2TokenReceiver.getAddress(); + + await distribution.stake(1, wei(1)); + + await depositToken.setTotalPooledEther((await depositToken.totalPooledEther()) * 2n); + + let overplus = await distribution.overplus(); + expect(overplus).to.eq(wei(1)); + + const fee = (overplus * wei(0.5, 25)) / wei(1, 25); + overplus -= fee; + + const bridgeMessageId = await distribution.bridgeOverplus.staticCall(1, 1, 1); + const tx = await distribution.bridgeOverplus(1, 1, 1); + await expect(tx).to.emit(distribution, 'OverplusBridged').withArgs(overplus, bridgeMessageId); + await expect(tx).to.changeTokenBalance(depositToken, distribution, wei(-1)); + expect(await wstETH.balanceOf(l2TokenReceiverAddress)).to.eq(overplus); + await expect(tx).to.changeTokenBalance(depositToken, OWNER, fee); + }); + it('should bridge full overplus if fee is 0', async () => { + await feeConfig.setBaseFee(0); + const l2TokenReceiverAddress = await l2TokenReceiver.getAddress(); await distribution.stake(1, wei(1)); @@ -1655,9 +1718,10 @@ describe('Distribution', () => { const bridgeMessageId = await distribution.bridgeOverplus.staticCall(1, 1, 1); const tx = await distribution.bridgeOverplus(1, 1, 1); - await expect(tx).to.emit(distribution, 'OverplusBridged').withArgs(wei(1), bridgeMessageId); + await expect(tx).to.emit(distribution, 'OverplusBridged').withArgs(overplus, bridgeMessageId); await expect(tx).to.changeTokenBalance(depositToken, distribution, wei(-1)); - expect(await wstETH.balanceOf(l2TokenReceiverAddress)).to.eq(wei(1)); + expect(await wstETH.balanceOf(l2TokenReceiverAddress)).to.eq(overplus); + await expect(tx).to.changeTokenBalance(depositToken, OWNER, 0); }); it('should revert if caller is not owner', async () => { await expect(distribution.connect(SECOND).bridgeOverplus(1, 1, 1)).to.be.revertedWith( diff --git a/test/L1/FeeConfig.test.ts b/test/L1/FeeConfig.test.ts new file mode 100644 index 0000000..d3f5fc1 --- /dev/null +++ b/test/L1/FeeConfig.test.ts @@ -0,0 +1,137 @@ +import { SignerWithAddress } from '@nomicfoundation/hardhat-ethers/signers'; +import { expect } from 'chai'; +import { ethers } from 'hardhat'; + +import { Reverter } from '../helpers/reverter'; + +import { FeeConfig, FeeConfigV2 } from '@/generated-types/ethers'; +import { ZERO_ADDR } from '@/scripts/utils/constants'; +import { wei } from '@/scripts/utils/utils'; + +describe('FeeConfig', () => { + let OWNER: SignerWithAddress; + let SECOND: SignerWithAddress; + + let feeConfig: FeeConfig; + + const reverter = new Reverter(); + + before(async () => { + [OWNER, SECOND] = await ethers.getSigners(); + + const [feeConfigFactory, ERC1967ProxyFactory] = await Promise.all([ + ethers.getContractFactory('FeeConfig'), + ethers.getContractFactory('ERC1967Proxy'), + ]); + + const feeConfigImpl = await feeConfigFactory.deploy(); + const feeConfigProxy = await ERC1967ProxyFactory.deploy(feeConfigImpl, '0x'); + feeConfig = feeConfigFactory.attach(feeConfigProxy) as FeeConfig; + + await feeConfig.__FeeConfig_init(OWNER, wei(0.1, 25)); + + await reverter.snapshot(); + }); + + afterEach(reverter.revert); + + describe('UUPS proxy functionality', () => { + describe('#__FeeConfig_init', () => { + it('should revert if try to call init function twice', async () => { + const reason = 'Initializable: contract is already initialized'; + + await expect(feeConfig.__FeeConfig_init(SECOND, wei(0.1, 25))).to.be.rejectedWith(reason); + }); + + describe('#_authorizeUpgrade', () => { + it('should correctly upgrade', async () => { + const feeConfigV2Factory = await ethers.getContractFactory('FeeConfigV2'); + const feeConfigV2Implementation = await feeConfigV2Factory.deploy(); + + await feeConfig.upgradeTo(feeConfigV2Implementation); + + const feeConfigV2 = feeConfigV2Factory.attach(await feeConfigV2Implementation.getAddress()) as FeeConfigV2; + + expect(await feeConfigV2.version()).to.eq(2); + }); + it('should revert if caller is not the owner', async () => { + await expect(feeConfig.connect(SECOND).upgradeTo(ZERO_ADDR)).to.be.revertedWith( + 'Ownable: caller is not the owner', + ); + }); + }); + }); + }); + + describe('#setFee', () => { + it('should set the fee', async () => { + expect(await feeConfig.fees(SECOND)).to.be.equal(0); + + await feeConfig.setFee(SECOND, wei(0.2, 25)); + + expect(await feeConfig.fees(SECOND)).to.be.equal(wei(0.2, 25)); + + await feeConfig.setFee(SECOND, wei(0.1, 25)); + + expect(await feeConfig.fees(SECOND)).to.be.equal(wei(0.1, 25)); + }); + it('should revert if not called by the owner', async () => { + await expect(feeConfig.connect(SECOND).setFee(SECOND, wei(0.1, 25))).to.be.revertedWith( + 'Ownable: caller is not the owner', + ); + }); + it('should revert if fee is greater than1', async () => { + await expect(feeConfig.setFee(SECOND, wei(1.1, 25))).to.be.revertedWith('FC: invalid fee'); + }); + }); + + describe('#setTreasury', () => { + it('should set the treasury', async () => { + await feeConfig.setTreasury(SECOND); + + expect(await feeConfig.treasury()).to.be.equal(SECOND); + }); + it('should revert if not called by the owner', async () => { + await expect(feeConfig.connect(SECOND).setTreasury(SECOND.address)).to.be.revertedWith( + 'Ownable: caller is not the owner', + ); + }); + it('should revert if treasury is zero address', async () => { + await expect(feeConfig.setTreasury(ZERO_ADDR)).to.be.revertedWith('FC: invalid treasury'); + }); + }); + + describe('#setBaseFee', () => { + it('should set the base fee', async () => { + await feeConfig.setBaseFee(wei(0.2, 25)); + + expect(await feeConfig.baseFee()).to.be.equal(wei(0.2, 25)); + }); + it('should revert if not called by the owner', async () => { + await expect(feeConfig.connect(SECOND).setBaseFee(wei(0.1, 25))).to.be.revertedWith( + 'Ownable: caller is not the owner', + ); + }); + it('should revert if fee is >= 1', async () => { + await expect(feeConfig.setBaseFee(wei(1, 25))).to.be.revertedWith('FC: invalid base fee'); + }); + }); + + describe('#getFeeAndTreasury', () => { + it('should return the base fee and treasury', async () => { + const [fee, treasury] = await feeConfig.getFeeAndTreasury(SECOND); + + expect(fee).to.be.equal(wei(0.1, 25)); + expect(treasury).to.be.equal(OWNER); + }); + it('should return the specific fee and treasury', async () => { + await feeConfig.setFee(SECOND, wei(0.2, 25)); + await feeConfig.setTreasury(SECOND); + + const [fee, treasury] = await feeConfig.getFeeAndTreasury(SECOND); + + expect(fee).to.be.equal(wei(0.2, 25)); + expect(treasury).to.be.equal(SECOND); + }); + }); +}); diff --git a/test/L1/L1Factory.test.ts b/test/L1/L1Factory.test.ts new file mode 100644 index 0000000..c24a92d --- /dev/null +++ b/test/L1/L1Factory.test.ts @@ -0,0 +1,369 @@ +import { SignerWithAddress } from '@nomicfoundation/hardhat-ethers/signers'; +import { expect } from 'chai'; +import { ethers } from 'hardhat'; + +import { PoolTypesL1 } from '../helpers/helper'; +import { Reverter } from '../helpers/reverter'; + +import { + Distribution, + Distribution__factory, + FeeConfig, + GatewayRouterMock, + IL1Factory, + L1Factory, + L1Sender, + L1Sender__factory, + LZEndpointMock, + LinearDistributionIntervalDecrease, + StETHMock, + WStETHMock, +} from '@/generated-types/ethers'; +import { L1FactoryV2 } from '@/generated-types/ethers/contracts/mock/L1FactoryV2'; +import { ZERO_ADDR } from '@/scripts/utils/constants'; +import { wei } from '@/scripts/utils/utils'; + +describe('L1Factory', () => { + const senderChainId = 101; + + const reverter = new Reverter(); + + let OWNER: SignerWithAddress; + let SECOND: SignerWithAddress; + + let l1Factory: L1Factory; + + let distributionFactory: Distribution__factory; + let l1SenderFactory: L1Sender__factory; + + let l1SenderImplementation: L1Sender; + let distributionImplementation: Distribution; + let feeConfig: FeeConfig; + + let stEthMock: StETHMock; + let wstEthMock: WStETHMock; + let gatewayRouterMock: GatewayRouterMock; + let lzEndpoint: LZEndpointMock; + + before(async () => { + [OWNER, SECOND] = await ethers.getSigners(); + + const [ + libFactory, + l1FactoryFactory, + stEthMockFactory, + wstEthMockFactory, + gatewayRouterMockFactory, + feeConfigFactory, + ERC1967ProxyFactory, + LZEndpointMockFactory, + ] = await Promise.all([ + ethers.getContractFactory('LinearDistributionIntervalDecrease'), + ethers.getContractFactory('L1Factory'), + ethers.getContractFactory('StETHMock'), + ethers.getContractFactory('WStETHMock'), + ethers.getContractFactory('GatewayRouterMock'), + ethers.getContractFactory('FeeConfig'), + ethers.getContractFactory('ERC1967Proxy'), + ethers.getContractFactory('LZEndpointMock'), + ]); + l1SenderFactory = await ethers.getContractFactory('L1Sender'); + + let lib: LinearDistributionIntervalDecrease; + [lib, l1SenderImplementation, stEthMock, gatewayRouterMock, lzEndpoint] = await Promise.all([ + libFactory.deploy(), + l1SenderFactory.deploy(), + stEthMockFactory.deploy(), + gatewayRouterMockFactory.deploy(), + LZEndpointMockFactory.deploy(senderChainId), + ]); + wstEthMock = await wstEthMockFactory.deploy(stEthMock); + + const feeConfigImpl = await feeConfigFactory.deploy(); + const feeConfigProxy = await ERC1967ProxyFactory.deploy(feeConfigImpl, '0x'); + feeConfig = feeConfigFactory.attach(feeConfigProxy) as FeeConfig; + + await feeConfig.__FeeConfig_init(OWNER, wei(0.1, 25)); + + distributionFactory = await ethers.getContractFactory('Distribution', { + libraries: { + LinearDistributionIntervalDecrease: lib, + }, + }); + distributionImplementation = await distributionFactory.deploy(); + + const factoryImpl = await l1FactoryFactory.deploy(); + const factoryProxy = await ERC1967ProxyFactory.deploy(factoryImpl, '0x'); + l1Factory = l1FactoryFactory.attach(factoryProxy) as L1Factory; + + await l1Factory.L1Factory_init(); + + const poolTypes = [PoolTypesL1.DISTRIBUTION, PoolTypesL1.L1_SENDER]; + const poolImplementations = [ + await distributionImplementation.getAddress(), + await l1SenderImplementation.getAddress(), + ]; + + await l1Factory.setImplementation(poolTypes[0], poolImplementations[0]); + await l1Factory.setImplementation(poolTypes[1], poolImplementations[1]); + + await reverter.snapshot(); + }); + + afterEach(reverter.revert); + + function getL1FactoryParams() { + const depositTokenExternalDeps: IL1Factory.DepositTokenExternalDepsStruct = { + token: stEthMock, + wToken: wstEthMock, + }; + + const lzExternalDeps: IL1Factory.LzExternalDepsStruct = { + endpoint: lzEndpoint, + zroPaymentAddress: ZERO_ADDR, + adapterParams: '0x', + destinationChainId: 2, + }; + + const arbExternalDeps: IL1Factory.ArbExternalDepsStruct = { + endpoint: gatewayRouterMock, + }; + + return { depositTokenExternalDeps, lzExternalDeps, arbExternalDeps }; + } + + function getL1DefaultParams() { + const l1Params: IL1Factory.L1ParamsStruct = { + protocolName: 'Mor20', + isNotUpgradeable: false, + poolsInfo: [], + l2TokenReceiver: SECOND, + l2MessageReceiver: SECOND, + }; + + return l1Params; + } + + describe('UUPS proxy functionality', () => { + describe('#L1Factory_init', () => { + it('should revert if try to call init function twice', async () => { + const reason = 'Initializable: contract is already initialized'; + + await expect(l1Factory.L1Factory_init()).to.be.rejectedWith(reason); + }); + + describe('#_authorizeUpgrade', () => { + it('should correctly upgrade', async () => { + const l1FactoryV2Factory = await ethers.getContractFactory('FactoryMockV2'); + const l1FactoryV2Implementation = await l1FactoryV2Factory.deploy(); + + await l1Factory.upgradeTo(l1FactoryV2Implementation); + + const l1factoryV2 = l1FactoryV2Factory.attach(await l1FactoryV2Implementation.getAddress()) as L1FactoryV2; + + expect(await l1factoryV2.version()).to.eq(2); + }); + it('should revert if caller is not the owner', async () => { + await expect(l1Factory.connect(SECOND).upgradeTo(ZERO_ADDR)).to.be.revertedWith( + 'Ownable: caller is not the owner', + ); + }); + }); + }); + }); + + describe('setDepositTokenExternalDeps', () => { + it('should set deposit token external deps', async () => { + const { depositTokenExternalDeps } = getL1FactoryParams(); + + await l1Factory.setDepositTokenExternalDeps(depositTokenExternalDeps); + + const actualDepositTokenExternalDeps = await l1Factory.depositTokenExternalDeps(); + expect(actualDepositTokenExternalDeps.token).to.equal(depositTokenExternalDeps.token); + expect(actualDepositTokenExternalDeps.wToken).to.equal(depositTokenExternalDeps.wToken); + }); + it('should revert if called by non-owner', async () => { + const { depositTokenExternalDeps } = getL1FactoryParams(); + + await expect(l1Factory.connect(SECOND).setDepositTokenExternalDeps(depositTokenExternalDeps)).to.be.revertedWith( + 'Ownable: caller is not the owner', + ); + }); + it('should revert if token is zero address', async () => { + const { depositTokenExternalDeps } = getL1FactoryParams(); + depositTokenExternalDeps.token = ZERO_ADDR; + + await expect(l1Factory.setDepositTokenExternalDeps(depositTokenExternalDeps)).to.be.revertedWith( + 'L1F: invalid token', + ); + }); + it('should revert if wToken is zero address', async () => { + const { depositTokenExternalDeps } = getL1FactoryParams(); + depositTokenExternalDeps.wToken = ZERO_ADDR; + + await expect(l1Factory.setDepositTokenExternalDeps(depositTokenExternalDeps)).to.be.revertedWith( + 'L1F: invalid wtoken', + ); + }); + }); + + describe('setLzExternalDeps', () => { + it('should set lz external deps', async () => { + const { lzExternalDeps } = getL1FactoryParams(); + + await l1Factory.setLzExternalDeps(lzExternalDeps); + + const actualLzExternalDeps = await l1Factory.lzExternalDeps(); + expect(actualLzExternalDeps.endpoint).to.equal(lzExternalDeps.endpoint); + expect(actualLzExternalDeps.zroPaymentAddress).to.equal(lzExternalDeps.zroPaymentAddress); + expect(actualLzExternalDeps.adapterParams).to.equal(lzExternalDeps.adapterParams); + expect(actualLzExternalDeps.destinationChainId).to.equal(lzExternalDeps.destinationChainId); + }); + it('should revert if called by non-owner', async () => { + const { lzExternalDeps } = getL1FactoryParams(); + + await expect(l1Factory.connect(SECOND).setLzExternalDeps(lzExternalDeps)).to.be.revertedWith( + 'Ownable: caller is not the owner', + ); + }); + it('should revert if endpoint is zero address', async () => { + const { lzExternalDeps } = getL1FactoryParams(); + lzExternalDeps.endpoint = ZERO_ADDR; + + await expect(l1Factory.setLzExternalDeps(lzExternalDeps)).to.be.revertedWith('L1F: invalid LZ endpoint'); + }); + it('should revert if destinationChainId is zero', async () => { + const { lzExternalDeps } = getL1FactoryParams(); + lzExternalDeps.destinationChainId = 0; + + await expect(l1Factory.setLzExternalDeps(lzExternalDeps)).to.be.revertedWith('L1F: invalid chain ID'); + }); + }); + + describe('setArbExternalDeps', () => { + it('should set arb external deps', async () => { + const { arbExternalDeps } = getL1FactoryParams(); + + await l1Factory.setArbExternalDeps(arbExternalDeps); + + const actualArbExternalDeps = await l1Factory.arbExternalDeps(); + expect(actualArbExternalDeps).to.equal(arbExternalDeps.endpoint); + }); + it('should revert if called by non-owner', async () => { + const { arbExternalDeps } = getL1FactoryParams(); + + await expect(l1Factory.connect(SECOND).setArbExternalDeps(arbExternalDeps)).to.be.revertedWith( + 'Ownable: caller is not the owner', + ); + }); + it('should revert if endpoint is zero address', async () => { + const { arbExternalDeps } = getL1FactoryParams(); + arbExternalDeps.endpoint = ZERO_ADDR; + + await expect(l1Factory.setArbExternalDeps(arbExternalDeps)).to.be.revertedWith('L1F: invalid ARB endpoint'); + }); + }); + + describe('#setFeeConfig', () => { + it('should set fee config', async () => { + await l1Factory.setFeeConfig(feeConfig); + + expect(await l1Factory.feeConfig()).to.equal(await feeConfig.getAddress()); + }); + it('should revert if provided fee config is zero address', async () => { + await expect(l1Factory.setFeeConfig(ZERO_ADDR)).to.be.revertedWith('L1F: invalid fee config'); + }); + it('should revert if called by non-owner', async () => { + await expect(l1Factory.connect(SECOND).setFeeConfig(feeConfig)).to.be.revertedWith( + 'Ownable: caller is not the owner', + ); + }); + }); + + describe('deploy', () => { + beforeEach(async () => { + const { depositTokenExternalDeps, lzExternalDeps, arbExternalDeps } = getL1FactoryParams(); + + await l1Factory.setDepositTokenExternalDeps(depositTokenExternalDeps); + await l1Factory.setLzExternalDeps(lzExternalDeps); + await l1Factory.setArbExternalDeps(arbExternalDeps); + }); + + it('should deploy', async () => { + const l1Params = getL1DefaultParams(); + + await l1Factory.deploy(l1Params); + + const distribution = distributionFactory.attach( + await l1Factory.deployedProxies(OWNER, l1Params.protocolName, PoolTypesL1.DISTRIBUTION), + ) as Distribution; + const l1Sender = l1SenderFactory.attach( + await l1Factory.deployedProxies(OWNER, l1Params.protocolName, PoolTypesL1.L1_SENDER), + ) as L1Sender; + + expect(await distribution.owner()).to.equal(OWNER); + expect(await l1Sender.owner()).to.equal(OWNER); + + expect(await distribution.depositToken()).to.equal((await l1Factory.depositTokenExternalDeps()).token); + expect(await distribution.l1Sender()).to.equal(l1Sender); + + expect(await l1Sender.unwrappedDepositToken()).to.equal((await l1Factory.depositTokenExternalDeps()).token); + expect(await l1Sender.distribution()).to.equal(distribution); + + const depositTokenConfig = await l1Sender.depositTokenConfig(); + expect(depositTokenConfig.token).to.equal((await l1Factory.depositTokenExternalDeps()).wToken); + expect(depositTokenConfig.gateway).to.equal(await l1Factory.arbExternalDeps()); + expect(depositTokenConfig.receiver).to.equal(l1Params.l2TokenReceiver); + + const rewardTokenConfig = await l1Sender.rewardTokenConfig(); + expect(rewardTokenConfig.gateway).to.equal((await l1Factory.lzExternalDeps()).endpoint); + expect(rewardTokenConfig.receiver).to.equal(l1Params.l2MessageReceiver); + expect(rewardTokenConfig.receiverChainId).to.equal((await l1Factory.lzExternalDeps()).destinationChainId); + expect(rewardTokenConfig.zroPaymentAddress).to.equal((await l1Factory.lzExternalDeps()).zroPaymentAddress); + expect(rewardTokenConfig.adapterParams).to.equal((await l1Factory.lzExternalDeps()).adapterParams); + }); + it('should remove upgradeable flag if isNotUpgradeable is true', async () => { + const l1Params = getL1DefaultParams(); + l1Params.isNotUpgradeable = true; + + await l1Factory.deploy(l1Params); + + const distribution = distributionFactory.attach( + await l1Factory.deployedProxies(OWNER, l1Params.protocolName, PoolTypesL1.DISTRIBUTION), + ) as Distribution; + + expect(await distribution.isNotUpgradeable()).to.be.true; + }); + it('should revert if contract is paused', async () => { + await l1Factory.pause(); + + await expect(l1Factory.deploy(getL1DefaultParams())).to.be.revertedWith('Pausable: paused'); + }); + }); + + describe('#predictAddresses', () => { + beforeEach(async () => { + const { depositTokenExternalDeps, lzExternalDeps, arbExternalDeps } = getL1FactoryParams(); + + await l1Factory.setDepositTokenExternalDeps(depositTokenExternalDeps); + await l1Factory.setLzExternalDeps(lzExternalDeps); + await l1Factory.setArbExternalDeps(arbExternalDeps); + }); + + it('should predict addresses', async () => { + const l1Params = getL1DefaultParams(); + + const [distribution, l1Sender] = await l1Factory.predictAddresses(l1Params.protocolName, OWNER); + + expect(distribution).to.be.properAddress; + expect(l1Sender).to.be.properAddress; + + await l1Factory.deploy(l1Params); + + expect(await l1Factory.deployedProxies(OWNER, l1Params.protocolName, PoolTypesL1.DISTRIBUTION)).to.equal( + distribution, + ); + expect(await l1Factory.deployedProxies(OWNER, l1Params.protocolName, PoolTypesL1.L1_SENDER)).to.equal(l1Sender); + }); + }); +}); diff --git a/test/L1/L1Sender.test.ts b/test/L1/L1Sender.test.ts index 9ee2132..9745748 100644 --- a/test/L1/L1Sender.test.ts +++ b/test/L1/L1Sender.test.ts @@ -9,11 +9,12 @@ import { L1SenderV2, L2MessageReceiver, LZEndpointMock, + LayerZeroEndpointV2Mock, + MOR20, StETHMock, WStETHMock, } from '@/generated-types/ethers'; import { ZERO_ADDR } from '@/scripts/utils/constants'; -import { wei } from '@/scripts/utils/utils'; import { Reverter } from '@/test/helpers/reverter'; describe('L1Sender', () => { @@ -30,13 +31,14 @@ describe('L1Sender', () => { let lZEndpointMockL1: LZEndpointMock; let lZEndpointMockL2: LZEndpointMock; + let lZEndpointMockOFT: LayerZeroEndpointV2Mock; let gatewayRouter: GatewayRouterMock; let l1Sender: L1Sender; let l2MessageReceiver: L2MessageReceiver; - let rewardToken: MOR; + let rewardToken: MOR20; before(async () => { [OWNER, SECOND] = await ethers.getSigners(); @@ -49,24 +51,27 @@ describe('L1Sender', () => { StETHMock, WStETHMock, L2MessageReceiver, + LZEndpointMockOFT, ] = await Promise.all([ ethers.getContractFactory('ERC1967Proxy'), ethers.getContractFactory('LZEndpointMock'), - ethers.getContractFactory('MOR'), + ethers.getContractFactory('MOR20'), ethers.getContractFactory('L1Sender'), ethers.getContractFactory('GatewayRouterMock'), ethers.getContractFactory('StETHMock'), ethers.getContractFactory('WStETHMock'), ethers.getContractFactory('L2MessageReceiver'), + ethers.getContractFactory('LayerZeroEndpointV2Mock'), ]); + lZEndpointMockOFT = await LZEndpointMockOFT.deploy(receiverChainId, OWNER); + let l1SenderImplementation: L1Sender; let l2MessageReceiverImplementation: L2MessageReceiver; [ lZEndpointMockL1, lZEndpointMockL2, - rewardToken, l1SenderImplementation, unwrappedToken, l2MessageReceiverImplementation, @@ -74,7 +79,6 @@ describe('L1Sender', () => { ] = await Promise.all([ LZEndpointMock.deploy(senderChainId), LZEndpointMock.deploy(receiverChainId), - Mor.deploy(wei(100)), L1Sender.deploy(), StETHMock.deploy(), L2MessageReceiver.deploy(), @@ -84,7 +88,8 @@ describe('L1Sender', () => { const l2MessageReceiverProxy = await ERC1967ProxyFactory.deploy(l2MessageReceiverImplementation, '0x'); l2MessageReceiver = L2MessageReceiver.attach(l2MessageReceiverProxy) as L2MessageReceiver; - await l2MessageReceiver.L2MessageReceiver__init(); + + rewardToken = await Mor.deploy('MOR', 'MOR', lZEndpointMockOFT, OWNER, l2MessageReceiver); const rewardTokenConfig: IL1Sender.RewardTokenConfigStruct = { gateway: lZEndpointMockL1, @@ -103,7 +108,7 @@ describe('L1Sender', () => { l1Sender = L1Sender.attach(l1SenderProxy) as L1Sender; await l1Sender.L1Sender__init(OWNER, rewardTokenConfig, depositTokenConfig); - await l2MessageReceiver.setParams(rewardToken, { + await l2MessageReceiver.L2MessageReceiver__init(rewardToken, { gateway: lZEndpointMockL2, sender: l1Sender, senderChainId: senderChainId, @@ -175,6 +180,22 @@ describe('L1Sender', () => { expect(await unwrappedToken.allowance(l1Sender, depositToken)).to.be.equal(ethers.MaxUint256); expect(await depositToken.allowance(l1Sender, gatewayRouter)).to.be.equal(ethers.MaxUint256); }); + it('should revert if receiver is zero address', async () => { + const [ERC1967ProxyFactory, L1Sender] = await Promise.all([ + ethers.getContractFactory('ERC1967Proxy'), + ethers.getContractFactory('L1Sender'), + ]); + const l1SenderImplementation = await L1Sender.deploy(); + const l1SenderProxy = await ERC1967ProxyFactory.deploy(l1SenderImplementation, '0x'); + const l1Sender = L1Sender.attach(l1SenderProxy) as L1Sender; + + await expect( + l1Sender.L1Sender__init(OWNER, rewardTokenConfig, { + ...depositTokenConfig, + receiver: ZERO_ADDR, + }), + ).to.be.rejectedWith('L1S: invalid receiver'); + }); }); describe('#_authorizeUpgrade', () => { @@ -198,182 +219,31 @@ describe('L1Sender', () => { describe('supportsInterface', () => { it('should support IL1Sender', async () => { - expect(await l1Sender.supportsInterface('0x0d3ba6cb')).to.be.true; + expect(await l1Sender.supportsInterface('0xb772d774')).to.be.true; }); it('should support IERC165', async () => { expect(await l1Sender.supportsInterface('0x01ffc9a7')).to.be.true; }); }); - describe('setDistribution', () => { - it('should set distribution', async () => { - await l1Sender.setDistribution(SECOND); - expect(await l1Sender.distribution()).to.be.equal(SECOND.address); + describe('setRewardTokenLZParams', () => { + it('should update lz params', async () => { + await l1Sender.setRewardTokenLZParams(SECOND, '0x1234'); + + const config = await l1Sender.rewardTokenConfig(); + expect(config.gateway).to.eq(await lZEndpointMockL1.getAddress()); + expect(config.receiver).to.eq(await l2MessageReceiver.getAddress()); + expect(config.receiverChainId).to.eq(receiverChainId); + expect(config.zroPaymentAddress).to.eq(await SECOND.getAddress()); + expect(config.adapterParams).to.eq('0x1234'); }); it('should revert if not called by the owner', async () => { - await expect(l1Sender.connect(SECOND).setDistribution(SECOND)).to.be.revertedWith( + await expect(l1Sender.connect(SECOND).setRewardTokenLZParams(SECOND, '0x1234')).to.be.revertedWith( 'Ownable: caller is not the owner', ); }); }); - describe('setRewardTokenConfig', () => { - it('should set new config', async () => { - const newConfig = { - gateway: l2MessageReceiver, - receiver: lZEndpointMockL1, - receiverChainId: 0, - zroPaymentAddress: ZERO_ADDR, - adapterParams: '0x', - }; - - await l1Sender.setRewardTokenConfig(newConfig); - - expect(await l1Sender.rewardTokenConfig()).to.be.deep.equal([ - await l2MessageReceiver.getAddress(), - await lZEndpointMockL1.getAddress(), - 0, - ZERO_ADDR, - '0x', - ]); - }); - it('should revert if not called by the owner', async () => { - await expect( - l1Sender.connect(SECOND).setRewardTokenConfig({ - gateway: lZEndpointMockL1, - receiver: l2MessageReceiver, - receiverChainId: receiverChainId, - zroPaymentAddress: ZERO_ADDR, - adapterParams: '0x', - }), - ).to.be.revertedWith('Ownable: caller is not the owner'); - }); - }); - - describe('setDepositTokenConfig', () => { - it('should reset allowances when token and gateway changed', async () => { - const [WStETHMock, GatewayRouterMock, StETHMock] = await Promise.all([ - ethers.getContractFactory('WStETHMock'), - ethers.getContractFactory('GatewayRouterMock'), - ethers.getContractFactory('StETHMock'), - ]); - - const newUnwrappedToken = await StETHMock.deploy(); - - const [newDepositToken, newGatewayRouter] = await Promise.all([ - WStETHMock.deploy(newUnwrappedToken), - GatewayRouterMock.deploy(), - ]); - - const newConfig = { - token: newDepositToken, - gateway: newGatewayRouter, - receiver: OWNER, - }; - - await l1Sender.setDepositTokenConfig(newConfig); - - expect(await l1Sender.depositTokenConfig()).to.be.deep.equal([ - await newDepositToken.getAddress(), - await newGatewayRouter.getAddress(), - OWNER.address, - ]); - - expect(await unwrappedToken.allowance(l1Sender, depositToken)).to.be.equal(0); - expect(await depositToken.allowance(l1Sender, gatewayRouter)).to.be.equal(0); - - expect(await newUnwrappedToken.allowance(l1Sender, newDepositToken)).to.be.equal(ethers.MaxUint256); - expect(await newDepositToken.allowance(l1Sender, newGatewayRouter)).to.be.equal(ethers.MaxUint256); - }); - it('should reset allowances when only token changed', async () => { - const [WStETHMock, StETHMock] = await Promise.all([ - ethers.getContractFactory('WStETHMock'), - ethers.getContractFactory('StETHMock'), - ]); - - const newUnwrappedToken = await StETHMock.deploy(); - const [newDepositToken] = await Promise.all([WStETHMock.deploy(newUnwrappedToken)]); - - const newConfig = { - token: newDepositToken, - gateway: gatewayRouter, - receiver: OWNER, - }; - - await l1Sender.setDepositTokenConfig(newConfig); - - expect(await l1Sender.depositTokenConfig()).to.be.deep.equal([ - await newDepositToken.getAddress(), - await gatewayRouter.getAddress(), - OWNER.address, - ]); - - expect(await unwrappedToken.allowance(l1Sender, depositToken)).to.be.equal(0); - expect(await depositToken.allowance(l1Sender, gatewayRouter)).to.be.equal(0); - - expect(await newUnwrappedToken.allowance(l1Sender, newDepositToken)).to.be.equal(ethers.MaxUint256); - }); - it('should reset allowances when only gateway changed', async () => { - const [GatewayRouterMock] = await Promise.all([ethers.getContractFactory('GatewayRouterMock')]); - const [newGatewayRouter] = await Promise.all([GatewayRouterMock.deploy()]); - - const newConfig = { - token: depositToken, - gateway: newGatewayRouter, - receiver: OWNER, - }; - - await l1Sender.setDepositTokenConfig(newConfig); - - expect(await l1Sender.depositTokenConfig()).to.be.deep.equal([ - await depositToken.getAddress(), - await newGatewayRouter.getAddress(), - OWNER.address, - ]); - - expect(await depositToken.allowance(l1Sender, gatewayRouter)).to.be.equal(0); - - expect(await unwrappedToken.allowance(l1Sender, depositToken)).to.be.equal(ethers.MaxUint256); - expect(await depositToken.allowance(l1Sender, newGatewayRouter)).to.be.equal(ethers.MaxUint256); - }); - it('should not change allowances when only receiver changed', async () => { - const newConfig = { - token: depositToken, - gateway: gatewayRouter, - receiver: SECOND, - }; - - await l1Sender.setDepositTokenConfig(newConfig); - - expect(await l1Sender.depositTokenConfig()).to.be.deep.equal([ - await depositToken.getAddress(), - await gatewayRouter.getAddress(), - SECOND.address, - ]); - - expect(await unwrappedToken.allowance(l1Sender, depositToken)).to.be.equal(ethers.MaxUint256); - expect(await depositToken.allowance(l1Sender, gatewayRouter)).to.be.equal(ethers.MaxUint256); - }); - it('should revert if not called by the owner', async () => { - await expect( - l1Sender.connect(OWNER).setDepositTokenConfig({ - token: depositToken, - gateway: gatewayRouter, - receiver: ZERO_ADDR, - }), - ).to.be.revertedWith('L1S: invalid receiver'); - }); - it('should revert if not called by the owner', async () => { - await expect( - l1Sender.connect(SECOND).setDepositTokenConfig({ - token: depositToken, - gateway: gatewayRouter, - receiver: SECOND, - }), - ).to.be.revertedWith('Ownable: caller is not the owner'); - }); - }); - describe('sendDepositToken', () => { it('should send tokens to another address', async () => { const l1SenderAddress = await l1Sender.getAddress(); @@ -398,57 +268,6 @@ describe('L1Sender', () => { l1Sender.connect(SECOND).sendMintMessage(SECOND, '999', OWNER, { value: ethers.parseEther('0.1') }), ).to.be.revertedWith('L1S: invalid sender'); }); - it('should not revert if not L2MessageReceiver sender', async () => { - await l2MessageReceiver.setParams(rewardToken, { - gateway: lZEndpointMockL2, - sender: OWNER, - senderChainId: senderChainId, - }); - - await l1Sender.sendMintMessage(SECOND, '999', OWNER, { value: ethers.parseEther('0.1') }); - expect(await rewardToken.balanceOf(SECOND)).to.eq(0); - }); - it('should `retryMessage` for failed message on the `L2MessageReceiver`', async () => { - const amount = '998'; - - // START send invalid call to L2MessageReceiver - // Set invalid sender in config - await l2MessageReceiver.setParams(rewardToken, { - gateway: lZEndpointMockL2, - sender: ZERO_ADDR, - senderChainId: senderChainId, - }); - - await l1Sender.sendMintMessage(SECOND, amount, OWNER, { value: ethers.parseEther('0.1') }); - expect(await rewardToken.balanceOf(SECOND)).to.eq('0'); - // END - - // Set valid sender in config - await l2MessageReceiver.setParams(rewardToken, { - gateway: lZEndpointMockL2, - sender: l1Sender, - senderChainId: senderChainId, - }); - - // Must send messages even though the previous one may be blocked - await l1Sender.sendMintMessage(SECOND, '1', OWNER, { value: ethers.parseEther('0.1') }); - expect(await rewardToken.balanceOf(SECOND)).to.eq('1'); - - // START retry to send invalid message - const senderAndReceiverAddress = ethers.solidityPacked( - ['address', 'address'], - [await l1Sender.getAddress(), await l2MessageReceiver.getAddress()], - ); - const payload = ethers.AbiCoder.defaultAbiCoder().encode(['address', 'uint256'], [SECOND.address, amount]); - - await l2MessageReceiver.retryMessage(senderChainId, senderAndReceiverAddress, 1, payload); - expect(await rewardToken.balanceOf(SECOND)).to.eq(Number(amount) + 1); - // END - - // Next messages shouldn't fail - await l1Sender.sendMintMessage(SECOND, '1', OWNER, { value: ethers.parseEther('0.1') }); - expect(await rewardToken.balanceOf(SECOND)).to.eq(Number(amount) + 2); - }); }); }); diff --git a/test/L2/L2Factory.test.ts b/test/L2/L2Factory.test.ts new file mode 100644 index 0000000..3d34329 --- /dev/null +++ b/test/L2/L2Factory.test.ts @@ -0,0 +1,328 @@ +import { SignerWithAddress } from '@nomicfoundation/hardhat-ethers/signers'; +import { expect } from 'chai'; +import { ethers } from 'hardhat'; + +import { PoolTypesL2 } from '../helpers/helper'; +import { Reverter } from '../helpers/reverter'; + +import { + IL2Factory, + L2Factory, + L2FactoryV2, + L2MessageReceiver, + L2MessageReceiver__factory, + L2TokenReceiver, + L2TokenReceiver__factory, + LZEndpointMock, + LayerZeroEndpointV2Mock, + MOR20, + MOR20Deployer, + MOR20__factory, + NonfungiblePositionManagerMock, + SwapRouterMock, +} from '@/generated-types/ethers'; +import { ETHER_ADDR, ZERO_ADDR } from '@/scripts/utils/constants'; + +describe('L2Factory', () => { + const senderChainId = 101; + + const reverter = new Reverter(); + + let OWNER: SignerWithAddress; + let SECOND: SignerWithAddress; + + let l2Factory: L2Factory; + + let lzEndpoint: LZEndpointMock; + let lzEndpointOFT: LayerZeroEndpointV2Mock; + + let swapRouter: SwapRouterMock; + let nonfungiblePositionManager: NonfungiblePositionManagerMock; + + let l2MessageReceiverFactory: L2MessageReceiver__factory; + let l2TokenReceiverFactory: L2TokenReceiver__factory; + let MOR20Factory: MOR20__factory; + + before(async () => { + [OWNER, SECOND] = await ethers.getSigners(); + + const [ + ERC1967ProxyFactory, + LZEndpointMockFactory, + LayerZeroEndpointV2Mock, + MOR20DeployerFactory, + SwapRouterMock, + NonfungiblePositionManagerMock, + ] = await Promise.all([ + ethers.getContractFactory('ERC1967Proxy'), + ethers.getContractFactory('LZEndpointMock'), + ethers.getContractFactory('LayerZeroEndpointV2Mock'), + ethers.getContractFactory('MOR20Deployer'), + ethers.getContractFactory('SwapRouterMock'), + ethers.getContractFactory('NonfungiblePositionManagerMock'), + ]); + + [l2MessageReceiverFactory, l2TokenReceiverFactory, MOR20Factory] = await Promise.all([ + ethers.getContractFactory('L2MessageReceiver'), + ethers.getContractFactory('L2TokenReceiver'), + ethers.getContractFactory('MOR20'), + ]); + + let l2MessageReceiver: L2MessageReceiver; + let l2TokenReceiver: L2TokenReceiver; + let MOR20Deployer: MOR20Deployer; + [ + lzEndpoint, + lzEndpointOFT, + l2MessageReceiver, + l2TokenReceiver, + MOR20Deployer, + swapRouter, + nonfungiblePositionManager, + ] = await Promise.all([ + LZEndpointMockFactory.deploy(senderChainId), + LayerZeroEndpointV2Mock.deploy(senderChainId, OWNER), + l2MessageReceiverFactory.deploy(), + l2TokenReceiverFactory.deploy(), + MOR20DeployerFactory.deploy(), + SwapRouterMock.deploy(), + NonfungiblePositionManagerMock.deploy(), + ]); + + const l2FactoryFactory = await ethers.getContractFactory('L2Factory', { + libraries: { + MOR20Deployer: MOR20Deployer, + }, + }); + const factoryImpl = await l2FactoryFactory.deploy(); + const factoryProxy = await ERC1967ProxyFactory.deploy(factoryImpl, '0x'); + l2Factory = l2FactoryFactory.attach(factoryProxy) as L2Factory; + + await l2Factory.L2Factory_init(); + + const poolTypes = [PoolTypesL2.L2_MESSAGE_RECEIVER, PoolTypesL2.L2_TOKEN_RECEIVER]; + const poolImplementations = [await l2MessageReceiver.getAddress(), await l2TokenReceiver.getAddress()]; + + await l2Factory.setImplementation(poolTypes[0], poolImplementations[0]); + await l2Factory.setImplementation(poolTypes[1], poolImplementations[1]); + + await reverter.snapshot(); + }); + + afterEach(reverter.revert); + + function getL2FactoryParams() { + const lzTokenExternalDeps: IL2Factory.LzExternalDepsStruct = { + endpoint: lzEndpoint, + oftEndpoint: lzEndpointOFT, + senderChainId: senderChainId, + }; + + const uniswapExternalDeps: IL2Factory.UniswapExternalDepsStruct = { + router: swapRouter, + nonfungiblePositionManager: nonfungiblePositionManager, + }; + + return { lzTokenExternalDeps, uniswapExternalDeps }; + } + + function getL2DefaultParams() { + const l2Params: IL2Factory.L2ParamsStruct = { + protocolName: 'Mor20', + mor20Name: 'MOR20', + mor20Symbol: 'M20', + l1Sender: ZERO_ADDR, + firstSwapParams_: { + tokenIn: ETHER_ADDR, + tokenOut: ETHER_ADDR, + fee: 100, + }, + secondSwapFee: 3000, + }; + + return l2Params; + } + + describe('UUPS proxy functionality', () => { + describe('#L2Factory_init', () => { + it('should revert if try to call init function twice', async () => { + const reason = 'Initializable: contract is already initialized'; + + await expect(l2Factory.L2Factory_init()).to.be.rejectedWith(reason); + }); + + describe('#_authorizeUpgrade', () => { + it('should correctly upgrade', async () => { + const l2FactoryV2Factory = await ethers.getContractFactory('FactoryMockV2'); + const l2FactoryV2Implementation = await l2FactoryV2Factory.deploy(); + + await l2Factory.upgradeTo(l2FactoryV2Implementation); + + const l2factoryV2 = l2FactoryV2Factory.attach(await l2FactoryV2Implementation.getAddress()) as L2FactoryV2; + + expect(await l2factoryV2.version()).to.eq(2); + }); + it('should revert if caller is not the owner', async () => { + await expect(l2Factory.connect(SECOND).upgradeTo(ZERO_ADDR)).to.be.revertedWith( + 'Ownable: caller is not the owner', + ); + }); + }); + }); + }); + + describe('setLzExternalDeps', () => { + it('should set lz external deps', async () => { + const { lzTokenExternalDeps } = getL2FactoryParams(); + + await l2Factory.setLzExternalDeps(lzTokenExternalDeps); + + const actualLzTokenExternalDeps = await l2Factory.lzExternalDeps(); + expect(actualLzTokenExternalDeps.endpoint).to.equal(lzTokenExternalDeps.endpoint); + expect(actualLzTokenExternalDeps.oftEndpoint).to.equal(lzTokenExternalDeps.oftEndpoint); + expect(actualLzTokenExternalDeps.senderChainId).to.equal(lzTokenExternalDeps.senderChainId); + }); + it('should revert if `endpoint` is zero address', async () => { + const { lzTokenExternalDeps } = getL2FactoryParams(); + lzTokenExternalDeps.endpoint = ZERO_ADDR; + + await expect(l2Factory.setLzExternalDeps(lzTokenExternalDeps)).to.be.revertedWith('L2F: invalid LZ endpoint'); + }); + it('should revert if `oftEndpoint` is zero address', async () => { + const { lzTokenExternalDeps } = getL2FactoryParams(); + lzTokenExternalDeps.oftEndpoint = ZERO_ADDR; + + await expect(l2Factory.setLzExternalDeps(lzTokenExternalDeps)).to.be.revertedWith('L2F: invalid LZ OFT endpoint'); + }); + it('should revert if `senderChainId` is zero', async () => { + const { lzTokenExternalDeps } = getL2FactoryParams(); + lzTokenExternalDeps.senderChainId = 0; + + await expect(l2Factory.setLzExternalDeps(lzTokenExternalDeps)).to.be.revertedWith('L2F: invalid chain ID'); + }); + it('should revert if caller is not the owner', async () => { + await expect( + l2Factory.connect(SECOND).setLzExternalDeps(getL2FactoryParams().lzTokenExternalDeps), + ).to.be.revertedWith('Ownable: caller is not the owner'); + }); + }); + + describe('#setUniswapExternalDeps', () => { + it('should set uniswap external deps', async () => { + const { uniswapExternalDeps } = getL2FactoryParams(); + + await l2Factory.setUniswapExternalDeps(uniswapExternalDeps); + + const actualUniswapExternalDeps = await l2Factory.uniswapExternalDeps(); + expect(actualUniswapExternalDeps.router).to.equal(uniswapExternalDeps.router); + expect(actualUniswapExternalDeps.nonfungiblePositionManager).to.equal( + uniswapExternalDeps.nonfungiblePositionManager, + ); + }); + it('should revert if `router` is zero address', async () => { + const { uniswapExternalDeps } = getL2FactoryParams(); + uniswapExternalDeps.router = ZERO_ADDR; + + await expect(l2Factory.setUniswapExternalDeps(uniswapExternalDeps)).to.be.revertedWith('L2F: invalid UNI router'); + }); + it('should revert if `nonfungiblePositionManager` is zero address', async () => { + const { uniswapExternalDeps } = getL2FactoryParams(); + uniswapExternalDeps.nonfungiblePositionManager = ZERO_ADDR; + + await expect(l2Factory.setUniswapExternalDeps(uniswapExternalDeps)).to.be.revertedWith('L2F: invalid NPM'); + }); + it('should revert if caller is not the owner', async () => { + await expect( + l2Factory.connect(SECOND).setUniswapExternalDeps(getL2FactoryParams().uniswapExternalDeps), + ).to.be.revertedWith('Ownable: caller is not the owner'); + }); + }); + + describe('#deploy', () => { + beforeEach(async () => { + const { lzTokenExternalDeps, uniswapExternalDeps } = getL2FactoryParams(); + + await l2Factory.setLzExternalDeps(lzTokenExternalDeps); + await l2Factory.setUniswapExternalDeps(uniswapExternalDeps); + }); + + it('should deploy', async () => { + const l2Params = getL2DefaultParams(); + + await l2Factory.deploy(l2Params); + + const l2MessageReceiver = l2MessageReceiverFactory.attach( + await l2Factory.deployedProxies(OWNER, l2Params.protocolName, PoolTypesL2.L2_MESSAGE_RECEIVER), + ) as L2MessageReceiver; + const l2TokenReceiver = l2TokenReceiverFactory.attach( + await l2Factory.deployedProxies(OWNER, l2Params.protocolName, PoolTypesL2.L2_TOKEN_RECEIVER), + ) as L2TokenReceiver; + const MOR = MOR20Factory.attach( + await l2Factory.deployedProxies(OWNER, l2Params.protocolName, PoolTypesL2.MOR20), + ) as MOR20; + + expect(await l2MessageReceiver.owner()).to.equal(OWNER); + expect(await l2TokenReceiver.owner()).to.equal(OWNER); + + // expect(await l2MessageReceiver.rewardToken()).to.equal(MOR); + const config = await l2MessageReceiver.config(); + expect(config.gateway).to.equal((await l2Factory.lzExternalDeps()).endpoint); + // expect(config.sender).to.equal(); + expect(config.senderChainId).to.equal((await l2Factory.lzExternalDeps()).senderChainId); + + expect(await l2TokenReceiver.router()).to.equal((await l2Factory.uniswapExternalDeps()).router); + expect(await l2TokenReceiver.nonfungiblePositionManager()).to.equal( + (await l2Factory.uniswapExternalDeps()).nonfungiblePositionManager, + ); + + const firstSwapParams = await l2TokenReceiver.firstSwapParams(); + expect(firstSwapParams.tokenIn).to.equal(l2Params.firstSwapParams_.tokenIn); + expect(firstSwapParams.tokenOut).to.equal(l2Params.firstSwapParams_.tokenOut); + expect(firstSwapParams.fee).to.equal(l2Params.firstSwapParams_.fee); + + const secondSwapParams = await l2TokenReceiver.secondSwapParams(); + expect(secondSwapParams.tokenIn).to.equal(l2Params.firstSwapParams_.tokenOut); + expect(secondSwapParams.tokenOut).to.equal(MOR); + expect(secondSwapParams.fee).to.equal(l2Params.secondSwapFee); + + expect(await MOR.owner()).to.equal(OWNER); + expect(await MOR.name()).to.equal(l2Params.mor20Name); + expect(await MOR.symbol()).to.equal(l2Params.mor20Symbol); + expect(await MOR.endpoint()).to.equal((await l2Factory.lzExternalDeps()).oftEndpoint); + expect(await MOR.isMinter(l2MessageReceiver)).to.be.true; + }); + it('should revert if contract is paused', async () => { + await l2Factory.pause(); + + await expect(l2Factory.deploy(getL2DefaultParams())).to.be.revertedWith('Pausable: paused'); + }); + }); + + describe('#predictAddresses', () => { + beforeEach(async () => { + const { lzTokenExternalDeps, uniswapExternalDeps } = getL2FactoryParams(); + + await l2Factory.setLzExternalDeps(lzTokenExternalDeps); + await l2Factory.setUniswapExternalDeps(uniswapExternalDeps); + }); + + it('should predict addresses', async () => { + const l2Params = getL2DefaultParams(); + + const [l2MessageReceiver, l2TokenReceiver] = await l2Factory.predictAddresses(l2Params.protocolName, OWNER); + + expect(l2MessageReceiver).to.be.properAddress; + expect(l2TokenReceiver).to.be.properAddress; + + await l2Factory.deploy(l2Params); + + expect(await l2Factory.deployedProxies(OWNER, l2Params.protocolName, PoolTypesL2.L2_MESSAGE_RECEIVER)).to.equal( + l2MessageReceiver, + ); + + expect(await l2Factory.deployedProxies(OWNER, l2Params.protocolName, PoolTypesL2.L2_TOKEN_RECEIVER)).to.equal( + l2TokenReceiver, + ); + }); + }); +}); diff --git a/test/L2/L2MessageReceiver.test.ts b/test/L2/L2MessageReceiver.test.ts index 9ff7245..2775749 100644 --- a/test/L2/L2MessageReceiver.test.ts +++ b/test/L2/L2MessageReceiver.test.ts @@ -2,7 +2,16 @@ import { SignerWithAddress } from '@nomicfoundation/hardhat-ethers/signers'; import { expect } from 'chai'; import { ethers } from 'hardhat'; -import { L2MessageReceiver, L2MessageReceiverV2, LayerZeroEndpointV2Mock, MOR, MOROFT } from '@/generated-types/ethers'; +import { + ERC1967Proxy__factory, + L2MessageReceiver, + L2MessageReceiverV2, + L2MessageReceiver__factory, + LayerZeroEndpointV2Mock, + LayerZeroEndpointV2Mock__factory, + MOR20, + MOR20__factory, +} from '@/generated-types/ethers'; import { ZERO_ADDR, ZERO_BYTES32 } from '@/scripts/utils/constants'; import { wei } from '@/scripts/utils/utils'; import { Reverter } from '@/test/helpers/reverter'; @@ -14,42 +23,39 @@ describe('L2MessageReceiver', () => { let SECOND: SignerWithAddress; let THIRD: SignerWithAddress; - // MOR let l2MessageReceiver: L2MessageReceiver; - let mor: MOR; - // MOROFT - let moroft: MOROFT; + let l2MessageReceiverFactory: L2MessageReceiver__factory; + let ERC1967ProxyFactory: ERC1967Proxy__factory; + + let mor: MOR20; let lZEndpointMock: LayerZeroEndpointV2Mock; + before(async () => { [OWNER, SECOND, THIRD] = await ethers.getSigners(); - const [ERC1967ProxyFactory, L2MessageReceiver, MOR, MOROFT, LayerZeroEndpointV2Mock] = await Promise.all([ + let MOR: MOR20__factory; + let LayerZeroEndpointV2Mock: LayerZeroEndpointV2Mock__factory; + [ERC1967ProxyFactory, l2MessageReceiverFactory, MOR, LayerZeroEndpointV2Mock] = await Promise.all([ ethers.getContractFactory('ERC1967Proxy'), ethers.getContractFactory('L2MessageReceiver'), - ethers.getContractFactory('MOR'), - ethers.getContractFactory('MOROFT'), + ethers.getContractFactory('MOR20'), ethers.getContractFactory('LayerZeroEndpointV2Mock'), ]); - mor = await MOR.deploy(wei(100)); - - const l2MessageReceiverImplementation = await L2MessageReceiver.deploy(); + const l2MessageReceiverImplementation = await l2MessageReceiverFactory.deploy(); const l2MessageReceiverProxy = await ERC1967ProxyFactory.deploy(l2MessageReceiverImplementation, '0x'); - l2MessageReceiver = L2MessageReceiver.attach(l2MessageReceiverProxy) as L2MessageReceiver; - await l2MessageReceiver.L2MessageReceiver__init(); + l2MessageReceiver = l2MessageReceiverFactory.attach(l2MessageReceiverProxy) as L2MessageReceiver; - await l2MessageReceiver.setParams(mor, { + // Setup ERC20MOR token + lZEndpointMock = await LayerZeroEndpointV2Mock.deploy(1, SECOND); + mor = await MOR.deploy('MOR', 'MOR', lZEndpointMock, OWNER, l2MessageReceiver); + + await l2MessageReceiver.L2MessageReceiver__init(mor, { gateway: THIRD, sender: OWNER, senderChainId: 2, }); - await mor.transferOwnership(l2MessageReceiver); - - // Setup MOROFT token - lZEndpointMock = await LayerZeroEndpointV2Mock.deploy(1, SECOND.address); - moroft = await MOROFT.deploy(await lZEndpointMock.getAddress(), SECOND.address, l2MessageReceiver); - await reverter.snapshot(); }); @@ -64,7 +70,13 @@ describe('L2MessageReceiver', () => { const l2MessageReceiver = await (await ethers.getContractFactory('L2MessageReceiver')).deploy(); - await expect(l2MessageReceiver.L2MessageReceiver__init()).to.be.rejectedWith(reason); + await expect( + l2MessageReceiver.L2MessageReceiver__init(ZERO_ADDR, { + gateway: ZERO_ADDR, + sender: ZERO_ADDR, + senderChainId: 0, + }), + ).to.be.rejectedWith(reason); }); }); @@ -72,7 +84,13 @@ describe('L2MessageReceiver', () => { it('should revert if try to call init function twice', async () => { const reason = 'Initializable: contract is already initialized'; - await expect(l2MessageReceiver.L2MessageReceiver__init()).to.be.rejectedWith(reason); + await expect( + l2MessageReceiver.L2MessageReceiver__init(ZERO_ADDR, { + gateway: ZERO_ADDR, + sender: ZERO_ADDR, + senderChainId: 0, + }), + ).to.be.rejectedWith(reason); }); }); @@ -95,25 +113,18 @@ describe('L2MessageReceiver', () => { }); }); - describe('#setParams', () => { - it('should set params', async () => { - await l2MessageReceiver.setParams(mor, { - gateway: ZERO_ADDR, - sender: SECOND, - senderChainId: 1, - }); - - expect(await l2MessageReceiver.rewardToken()).to.be.equal(await mor.getAddress()); - expect(await l2MessageReceiver.config()).to.be.deep.equal([ZERO_ADDR, SECOND.address, 1n]); + describe('#setLzSender', async () => { + it('should set the sender', async () => { + await l2MessageReceiver.setLzSender(SECOND); + expect((await l2MessageReceiver.config()).sender).to.eq(SECOND); }); - it('should revert if not owner', async () => { - await expect( - l2MessageReceiver.connect(SECOND).setParams(ZERO_ADDR, { - gateway: ZERO_ADDR, - sender: OWNER, - senderChainId: 0, - }), - ).to.be.revertedWith('Ownable: caller is not the owner'); + it('should revert if not called by the owner', async () => { + await expect(l2MessageReceiver.connect(SECOND).setLzSender(SECOND)).to.be.revertedWith( + 'Ownable: caller is not the owner', + ); + }); + it('should revert if provided zero address', async () => { + await expect(l2MessageReceiver.setLzSender(ZERO_ADDR)).to.be.revertedWith('L2MR: invalid sender'); }); }); @@ -140,11 +151,13 @@ describe('L2MessageReceiver', () => { await expect(tx).to.changeTokenBalance(mor, SECOND, wei(2)); payload = ethers.AbiCoder.defaultAbiCoder().encode(['address', 'uint256'], [SECOND.address, wei(5)]); tx = await l2MessageReceiver.connect(THIRD).lzReceive(2, address, 7, payload); - await expect(tx).to.changeTokenBalance(mor, SECOND, 0); - expect(await l2MessageReceiver.failedMessages(2, address, 7)).to.eq(ethers.keccak256(payload)); }); - it('should mint tokens, MOROFT', async () => { - await l2MessageReceiver.setParams(moroft, { + it('should mint tokens, ERC20MOR', async () => { + const l2MessageReceiverImplementation = await l2MessageReceiverFactory.deploy(); + const l2MessageReceiverProxy = await ERC1967ProxyFactory.deploy(l2MessageReceiverImplementation, '0x'); + const l2MessageReceiver = l2MessageReceiverFactory.attach(l2MessageReceiverProxy) as L2MessageReceiver; + await mor.updateMinter(l2MessageReceiver, true); + await l2MessageReceiver.L2MessageReceiver__init(mor, { gateway: THIRD, sender: OWNER, senderChainId: 2, @@ -156,52 +169,15 @@ describe('L2MessageReceiver', () => { ); let payload = ethers.AbiCoder.defaultAbiCoder().encode(['address', 'uint256'], [SECOND.address, wei(95)]); let tx = await l2MessageReceiver.connect(THIRD).lzReceive(2, address, 5, payload); - await expect(tx).to.changeTokenBalance(moroft, SECOND, wei(95)); + await expect(tx).to.changeTokenBalance(mor, SECOND, wei(95)); payload = ethers.AbiCoder.defaultAbiCoder().encode(['address', 'uint256'], [SECOND.address, wei(2)]); tx = await l2MessageReceiver.connect(THIRD).lzReceive(2, address, 6, payload); - await expect(tx).to.changeTokenBalance(moroft, SECOND, wei(2)); + await expect(tx).to.changeTokenBalance(mor, SECOND, wei(2)); payload = ethers.AbiCoder.defaultAbiCoder().encode(['address', 'uint256'], [SECOND.address, wei(5)]); }); it('should revert if provided wrong lzEndpoint', async () => { await expect(l2MessageReceiver.lzReceive(0, '0x', 1, '0x')).to.be.revertedWith('L2MR: invalid gateway'); }); - it('should fail if provided wrong mint amount', async () => { - const address = ethers.solidityPacked( - ['address', 'address'], - [OWNER.address, await l2MessageReceiver.getAddress()], - ); - const payload = ethers.AbiCoder.defaultAbiCoder().encode(['address', 'uint256'], [SECOND.address, wei(100)]); - - let tx = await l2MessageReceiver.connect(THIRD).lzReceive(2, address, 5, payload); - await expect(tx).to.changeTokenBalance(mor, SECOND, wei(100)); - - expect(await l2MessageReceiver.failedMessages(2, address, 5)).to.eq(ZERO_BYTES32); - await l2MessageReceiver.connect(THIRD).lzReceive(2, address, 5, payload); - expect(await l2MessageReceiver.failedMessages(2, address, 5)).to.eq(ethers.keccak256(payload)); - - await mor.connect(SECOND).burn(wei(100)); - - tx = await l2MessageReceiver.connect(THIRD).lzReceive(2, address, 6, payload); - expect(await l2MessageReceiver.failedMessages(2, address, 6)).to.eq(ZERO_BYTES32); - await expect(tx).to.changeTokenBalance(mor, SECOND, wei(100)); - }); - it('should fail if provided wrong mint amount', async () => { - const address = ethers.solidityPacked( - ['address', 'address'], - [OWNER.address, await l2MessageReceiver.getAddress()], - ); - const payload = ethers.AbiCoder.defaultAbiCoder().encode(['address', 'uint256'], [SECOND.address, wei(100)]); - - await l2MessageReceiver.connect(THIRD).lzReceive(2, address, 5, payload); - - expect(await l2MessageReceiver.failedMessages(2, address, 5)).to.eq(ZERO_BYTES32); - await l2MessageReceiver.connect(THIRD).lzReceive(2, address, 5, payload); - expect(await l2MessageReceiver.failedMessages(2, address, 5)).to.eq(ethers.keccak256(payload)); - - await mor.connect(SECOND).burn(wei(100)); - - await l2MessageReceiver.connect(THIRD).lzReceive(2, address, 5, payload); - }); }); describe('#nonblockingLzReceive', () => { @@ -211,32 +187,43 @@ describe('L2MessageReceiver', () => { }); describe('#retryMessage', () => { - let senderAndReceiverAddresses = ''; let payload = ''; const chainId = 2; beforeEach(async () => { - senderAndReceiverAddresses = ethers.solidityPacked( + payload = ethers.AbiCoder.defaultAbiCoder().encode(['address', 'uint256'], [SECOND.address, wei(99)]); + }); + it('should have one blocked message', async () => { + const senderAndReceiverAddresses = ethers.solidityPacked( ['address', 'address'], [SECOND.address, await l2MessageReceiver.getAddress()], ); - payload = ethers.AbiCoder.defaultAbiCoder().encode(['address', 'uint256'], [SECOND.address, wei(99)]); - // Fail this call await l2MessageReceiver.connect(THIRD).lzReceive(chainId, senderAndReceiverAddresses, 999, payload); - }); - it('should have one blocked message', async () => { + expect(await l2MessageReceiver.failedMessages(chainId, senderAndReceiverAddresses, 999)).to.eq( ethers.keccak256(payload), ); }); it('should retry failed message', async () => { - await l2MessageReceiver.setParams(mor, { + const l2MessageReceiverImplementation = await l2MessageReceiverFactory.deploy(); + const l2MessageReceiverProxy = await ERC1967ProxyFactory.deploy(l2MessageReceiverImplementation, '0x'); + const l2MessageReceiver = l2MessageReceiverFactory.attach(l2MessageReceiverProxy) as L2MessageReceiver; + await l2MessageReceiver.L2MessageReceiver__init(mor, { gateway: THIRD, sender: SECOND, senderChainId: 2, }); + const senderAndReceiverAddresses = ethers.solidityPacked( + ['address', 'address'], + [SECOND.address, await l2MessageReceiver.getAddress()], + ); + // Fail this call + await l2MessageReceiver.connect(THIRD).lzReceive(chainId, senderAndReceiverAddresses, 999, payload); + + await mor.updateMinter(l2MessageReceiver, true); + const tx = await l2MessageReceiver.retryMessage(chainId, senderAndReceiverAddresses, 999, payload); await expect(tx).to.changeTokenBalance(mor, SECOND, wei(99)); @@ -248,38 +235,78 @@ describe('L2MessageReceiver', () => { ); }); it('should revert if provided wrong chainId', async () => { - await l2MessageReceiver.setParams(mor, { + const l2MessageReceiverImplementation = await l2MessageReceiverFactory.deploy(); + const l2MessageReceiverProxy = await ERC1967ProxyFactory.deploy(l2MessageReceiverImplementation, '0x'); + const l2MessageReceiver = l2MessageReceiverFactory.attach(l2MessageReceiverProxy) as L2MessageReceiver; + await mor.updateMinter(l2MessageReceiver, true); + await l2MessageReceiver.L2MessageReceiver__init(mor, { gateway: THIRD, sender: SECOND, senderChainId: 3, }); + const senderAndReceiverAddresses = ethers.solidityPacked( + ['address', 'address'], + [SECOND.address, await l2MessageReceiver.getAddress()], + ); + // Fail this call + await l2MessageReceiver.connect(THIRD).lzReceive(chainId, senderAndReceiverAddresses, 999, payload); + await expect( l2MessageReceiver.retryMessage(chainId, senderAndReceiverAddresses, 999, payload), ).to.be.revertedWith('L2MR: invalid sender chain ID'); }); it('should revert if provided wrong sender', async () => { + const senderAndReceiverAddresses = ethers.solidityPacked( + ['address', 'address'], + [SECOND.address, await l2MessageReceiver.getAddress()], + ); + // Fail this call + await l2MessageReceiver.connect(THIRD).lzReceive(chainId, senderAndReceiverAddresses, 999, payload); + await expect( l2MessageReceiver.retryMessage(chainId, senderAndReceiverAddresses, 999, payload), ).to.be.revertedWith('L2MR: invalid sender address'); }); it('should revert if provided wrong message', async () => { + const senderAndReceiverAddresses = ethers.solidityPacked( + ['address', 'address'], + [SECOND.address, await l2MessageReceiver.getAddress()], + ); await expect( l2MessageReceiver.retryMessage(chainId, senderAndReceiverAddresses, 998, payload), ).to.be.revertedWith('L2MR: no stored message'); }); it('should revert if provided wrong payload', async () => { + const senderAndReceiverAddresses = ethers.solidityPacked( + ['address', 'address'], + [SECOND.address, await l2MessageReceiver.getAddress()], + ); + // Fail this call + await l2MessageReceiver.connect(THIRD).lzReceive(chainId, senderAndReceiverAddresses, 999, payload); + await expect(l2MessageReceiver.retryMessage(chainId, senderAndReceiverAddresses, 999, '0x')).to.be.revertedWith( 'L2MR: invalid payload', ); }); it('should revert if try to retry already retried message', async () => { - await l2MessageReceiver.setParams(mor, { + const l2MessageReceiverImplementation = await l2MessageReceiverFactory.deploy(); + const l2MessageReceiverProxy = await ERC1967ProxyFactory.deploy(l2MessageReceiverImplementation, '0x'); + const l2MessageReceiver = l2MessageReceiverFactory.attach(l2MessageReceiverProxy) as L2MessageReceiver; + await l2MessageReceiver.L2MessageReceiver__init(mor, { gateway: THIRD, sender: SECOND, senderChainId: 2, }); + const senderAndReceiverAddresses = ethers.solidityPacked( + ['address', 'address'], + [SECOND.address, await l2MessageReceiver.getAddress()], + ); + // Fail this call + await l2MessageReceiver.connect(THIRD).lzReceive(chainId, senderAndReceiverAddresses, 999, payload); + await mor.updateMinter(l2MessageReceiver, true); + await l2MessageReceiver.retryMessage(chainId, senderAndReceiverAddresses, 999, payload); await expect( diff --git a/test/L2/L2TokenReceiver.test.ts b/test/L2/L2TokenReceiver.test.ts index d1bce70..d7391cd 100644 --- a/test/L2/L2TokenReceiver.test.ts +++ b/test/L2/L2TokenReceiver.test.ts @@ -9,12 +9,13 @@ import { IL2TokenReceiver, L2TokenReceiver, L2TokenReceiverV2, + LayerZeroEndpointV2Mock, + MOR20, NonfungiblePositionManagerMock, StETHMock, SwapRouterMock, } from '@/generated-types/ethers'; import { ZERO_ADDR } from '@/scripts/utils/constants'; -import { wei } from '@/scripts/utils/utils'; describe('L2TokenReceiver', () => { const reverter = new Reverter(); @@ -23,31 +24,43 @@ describe('L2TokenReceiver', () => { let OWNER: SignerWithAddress; let SECOND: SignerWithAddress; + let lZEndpointMockOFT: LayerZeroEndpointV2Mock; + let swapRouter: SwapRouterMock; let nonfungiblePositionManager: NonfungiblePositionManagerMock; let l2TokenReceiver: L2TokenReceiver; let inputToken: StETHMock; - let outputToken: MOR; + let outputToken: MOR20; before(async () => { [OWNER, SECOND] = await ethers.getSigners(); - const [ERC1967ProxyFactory, L2TokenReceiver, StETHMock, Mor, SwapRouterMock, NonfungiblePositionManagerMock] = - await Promise.all([ - ethers.getContractFactory('ERC1967Proxy'), - ethers.getContractFactory('L2TokenReceiver'), - ethers.getContractFactory('StETHMock'), - ethers.getContractFactory('MOR'), - ethers.getContractFactory('SwapRouterMock'), - ethers.getContractFactory('NonfungiblePositionManagerMock'), - ]); + const [ + ERC1967ProxyFactory, + L2TokenReceiver, + StETHMock, + Mor, + SwapRouterMock, + NonfungiblePositionManagerMock, + LZEndpointMockOFT, + ] = await Promise.all([ + ethers.getContractFactory('ERC1967Proxy'), + ethers.getContractFactory('L2TokenReceiver'), + ethers.getContractFactory('StETHMock'), + ethers.getContractFactory('MOR20'), + ethers.getContractFactory('SwapRouterMock'), + ethers.getContractFactory('NonfungiblePositionManagerMock'), + ethers.getContractFactory('LayerZeroEndpointV2Mock'), + ]); let l2TokenReceiverImplementation: L2TokenReceiver; + lZEndpointMockOFT = await LZEndpointMockOFT.deploy(1, OWNER); + [inputToken, outputToken, swapRouter, nonfungiblePositionManager, l2TokenReceiverImplementation] = await Promise.all([ StETHMock.deploy(), - Mor.deploy(wei(100)), + Mor.deploy('MOR', 'MOR', lZEndpointMockOFT, OWNER, OWNER), SwapRouterMock.deploy(), NonfungiblePositionManagerMock.deploy(), L2TokenReceiver.deploy(), @@ -55,12 +68,20 @@ describe('L2TokenReceiver', () => { const l2TokenReceiverProxy = await ERC1967ProxyFactory.deploy(l2TokenReceiverImplementation, '0x'); l2TokenReceiver = L2TokenReceiver.attach(l2TokenReceiverProxy) as L2TokenReceiver; - await l2TokenReceiver.L2TokenReceiver__init(swapRouter, nonfungiblePositionManager, { - tokenIn: inputToken, - tokenOut: outputToken, - fee: 500, - sqrtPriceLimitX96: 0, - }); + await l2TokenReceiver.L2TokenReceiver__init( + swapRouter, + nonfungiblePositionManager, + { + tokenIn: inputToken, + tokenOut: outputToken, + fee: 500, + }, + { + tokenIn: inputToken, + tokenOut: outputToken, + fee: 500, + }, + ); await reverter.snapshot(); }); @@ -76,12 +97,20 @@ describe('L2TokenReceiver', () => { const l2TokenReceiver = await (await ethers.getContractFactory('L2TokenReceiver')).deploy(); await expect( - l2TokenReceiver.L2TokenReceiver__init(swapRouter, nonfungiblePositionManager, { - tokenIn: inputToken, - tokenOut: outputToken, - fee: 500, - sqrtPriceLimitX96: 0, - }), + l2TokenReceiver.L2TokenReceiver__init( + swapRouter, + nonfungiblePositionManager, + { + tokenIn: inputToken, + tokenOut: outputToken, + fee: 500, + }, + { + tokenIn: inputToken, + tokenOut: outputToken, + fee: 500, + }, + ), ).to.be.rejectedWith(reason); }); @@ -90,12 +119,20 @@ describe('L2TokenReceiver', () => { const reason = 'Initializable: contract is already initialized'; await expect( - l2TokenReceiver.L2TokenReceiver__init(swapRouter, nonfungiblePositionManager, { - tokenIn: inputToken, - tokenOut: outputToken, - fee: 500, - sqrtPriceLimitX96: 0, - }), + l2TokenReceiver.L2TokenReceiver__init( + swapRouter, + nonfungiblePositionManager, + { + tokenIn: inputToken, + tokenOut: outputToken, + fee: 500, + }, + { + tokenIn: inputToken, + tokenOut: outputToken, + fee: 500, + }, + ), ).to.be.rejectedWith(reason); }); it('should set router', async () => { @@ -103,12 +140,11 @@ describe('L2TokenReceiver', () => { }); it('should set params', async () => { const defaultParams = getDefaultSwapParams(await inputToken.getAddress(), await outputToken.getAddress()); - const params = await l2TokenReceiver.params(); + const params = await l2TokenReceiver.secondSwapParams(); expect(params.tokenIn).to.equal(defaultParams.tokenIn); expect(params.tokenOut).to.equal(defaultParams.tokenOut); expect(params.fee).to.equal(defaultParams.fee); - expect(params.sqrtPriceLimitX96).to.equal(defaultParams.sqrtPriceLimitX96); }); it('should give allowance', async () => { expect(await inputToken.allowance(l2TokenReceiver, swapRouter)).to.equal(ethers.MaxUint256); @@ -138,7 +174,7 @@ describe('L2TokenReceiver', () => { describe('supportsInterface', () => { it('should support IL2TokenReceiver', async () => { - expect(await l2TokenReceiver.supportsInterface('0x067ef141')).to.be.true; + expect(await l2TokenReceiver.supportsInterface('0xe15df538')).to.be.true; }); it('should support IERC165', async () => { expect(await l2TokenReceiver.supportsInterface('0x01ffc9a7')).to.be.true; @@ -154,27 +190,24 @@ describe('L2TokenReceiver', () => { tokenIn: await outputToken.getAddress(), tokenOut: await inputToken.getAddress(), fee: 1, - sqrtPriceLimitX96: 1, }; - await l2TokenReceiver.editParams(newParams); + await l2TokenReceiver.editParams(newParams, false); - const params = await l2TokenReceiver.params(); + const params = await l2TokenReceiver.secondSwapParams(); expect(params.tokenIn).to.equal(newParams.tokenIn); expect(params.tokenOut).to.equal(newParams.tokenOut); expect(params.fee).to.equal(newParams.fee); - expect(params.sqrtPriceLimitX96).to.equal(newParams.sqrtPriceLimitX96); }); it('should set new allowance', async () => { const newParams: IL2TokenReceiver.SwapParamsStruct = { tokenIn: await outputToken.getAddress(), tokenOut: await inputToken.getAddress(), fee: 1, - sqrtPriceLimitX96: 1, }; - await l2TokenReceiver.editParams(newParams); + await l2TokenReceiver.editParams(newParams, false); expect(await inputToken.allowance(l2TokenReceiver, swapRouter)).to.equal(0); expect(await inputToken.allowance(l2TokenReceiver, nonfungiblePositionManager)).to.equal(ethers.MaxUint256); @@ -183,24 +216,24 @@ describe('L2TokenReceiver', () => { }); it('should revert if caller is not owner', async () => { await expect( - l2TokenReceiver.connect(SECOND).editParams(getDefaultSwapParams(ZERO_ADDR, ZERO_ADDR)), + l2TokenReceiver.connect(SECOND).editParams(getDefaultSwapParams(ZERO_ADDR, ZERO_ADDR), false), ).to.be.revertedWith('Ownable: caller is not the owner'); }); it('should revert if tokenIn is zero address', async () => { await expect( - l2TokenReceiver.editParams(getDefaultSwapParams(ZERO_ADDR, await outputToken.getAddress())), + l2TokenReceiver.editParams(getDefaultSwapParams(ZERO_ADDR, await outputToken.getAddress()), false), ).to.be.revertedWith('L2TR: invalid tokenIn'); }); it('should revert if tokenOut is zero address', async () => { await expect( - l2TokenReceiver.editParams(getDefaultSwapParams(await inputToken.getAddress(), ZERO_ADDR)), + l2TokenReceiver.editParams(getDefaultSwapParams(await inputToken.getAddress(), ZERO_ADDR), false), ).to.be.revertedWith('L2TR: invalid tokenOut'); }); }); describe('#swap', () => { it('should return if caller is not the owner', async () => { - await expect(l2TokenReceiver.connect(SECOND).swap(1, 1, 1)).to.be.revertedWith( + await expect(l2TokenReceiver.connect(SECOND).swap(1, 1, 1, 0, false)).to.be.revertedWith( 'Ownable: caller is not the owner', ); }); @@ -213,6 +246,37 @@ describe('L2TokenReceiver', () => { ); }); }); + + describe('#decreaseLiquidityCurrentRange', () => { + it('should return if caller is not the owner', async () => { + await expect(l2TokenReceiver.connect(SECOND).decreaseLiquidityCurrentRange(1, 1, 0, 0)).to.be.revertedWith( + 'Ownable: caller is not the owner', + ); + }); + }); + + describe('#withdrawToken', () => { + it('should withdraw token', async () => { + await inputToken.mint(l2TokenReceiver, 1); + + const tx = await l2TokenReceiver.withdrawToken(OWNER, inputToken, 1); + + await expect(tx).to.changeTokenBalances(inputToken, [l2TokenReceiver, OWNER], [-1, 1]); + }); + it('should return if caller is not the owner', async () => { + await expect(l2TokenReceiver.connect(SECOND).withdrawToken(OWNER, inputToken, 1)).to.be.revertedWith( + 'Ownable: caller is not the owner', + ); + }); + }); + + describe('#withdrawTokenId', () => { + it('should return if caller is not the owner', async () => { + await expect(l2TokenReceiver.connect(SECOND).withdrawTokenId(OWNER, OWNER, 0)).to.be.revertedWith( + 'Ownable: caller is not the owner', + ); + }); + }); }); // npx hardhat test "test/L2TokenReceiver.test.ts" diff --git a/test/L2/L2TokenReceiverV2.test.ts b/test/L2/L2TokenReceiverV2.test.ts deleted file mode 100644 index 4a3b00b..0000000 --- a/test/L2/L2TokenReceiverV2.test.ts +++ /dev/null @@ -1,254 +0,0 @@ -import { SignerWithAddress } from '@nomicfoundation/hardhat-ethers/signers'; -import { expect } from 'chai'; -import { ethers } from 'hardhat'; - -import { getDefaultSwapParams } from '../helpers/distribution-helper'; -import { Reverter } from '../helpers/reverter'; - -import { - IL2TokenReceiverV2, - L2TokenReceiverV2, - NonfungiblePositionManagerMock, - StETHMock, - SwapRouterMock, -} from '@/generated-types/ethers'; -import { ZERO_ADDR } from '@/scripts/utils/constants'; -import { wei } from '@/scripts/utils/utils'; - -describe('L2TokenReceiverV2', () => { - const reverter = new Reverter(); - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - let OWNER: SignerWithAddress; - let SECOND: SignerWithAddress; - - let swapRouter: SwapRouterMock; - let nonfungiblePositionManager: NonfungiblePositionManagerMock; - - let l2TokenReceiver: L2TokenReceiverV2; - let inputToken: StETHMock; - let outputToken: MOR; - before(async () => { - [OWNER, SECOND] = await ethers.getSigners(); - - const [ERC1967ProxyFactory, L2TokenReceiver, StETHMock, Mor, SwapRouterMock, NonfungiblePositionManagerMock] = - await Promise.all([ - ethers.getContractFactory('ERC1967Proxy'), - ethers.getContractFactory('L2TokenReceiverV2'), - ethers.getContractFactory('StETHMock'), - ethers.getContractFactory('MOR'), - ethers.getContractFactory('SwapRouterMock'), - ethers.getContractFactory('NonfungiblePositionManagerMock'), - ]); - - let l2TokenReceiverImplementation: L2TokenReceiverV2; - - [inputToken, outputToken, swapRouter, nonfungiblePositionManager, l2TokenReceiverImplementation] = - await Promise.all([ - StETHMock.deploy(), - Mor.deploy(wei(100)), - SwapRouterMock.deploy(), - NonfungiblePositionManagerMock.deploy(), - L2TokenReceiver.deploy(), - ]); - - const l2TokenReceiverProxy = await ERC1967ProxyFactory.deploy(l2TokenReceiverImplementation, '0x'); - l2TokenReceiver = L2TokenReceiver.attach(l2TokenReceiverProxy) as L2TokenReceiverV2; - await l2TokenReceiver.L2TokenReceiver__init(swapRouter, nonfungiblePositionManager, { - tokenIn: inputToken, - tokenOut: outputToken, - fee: 500, - sqrtPriceLimitX96: 0, - }); - - await reverter.snapshot(); - }); - - beforeEach(async () => { - await reverter.revert(); - }); - - describe('UUPS proxy functionality', () => { - it('should disable initialize function', async () => { - const reason = 'Initializable: contract is already initialized'; - - const l2TokenReceiver = await (await ethers.getContractFactory('L2TokenReceiver')).deploy(); - - await expect( - l2TokenReceiver.L2TokenReceiver__init(swapRouter, nonfungiblePositionManager, { - tokenIn: inputToken, - tokenOut: outputToken, - fee: 500, - sqrtPriceLimitX96: 0, - }), - ).to.be.rejectedWith(reason); - }); - - describe('#L2TokenReceiver__init', () => { - it('should revert if try to call init function twice', async () => { - const reason = 'Initializable: contract is already initialized'; - - await expect( - l2TokenReceiver.L2TokenReceiver__init(swapRouter, nonfungiblePositionManager, { - tokenIn: inputToken, - tokenOut: outputToken, - fee: 500, - sqrtPriceLimitX96: 0, - }), - ).to.be.rejectedWith(reason); - }); - it('should set router', async () => { - expect(await l2TokenReceiver.router()).to.equal(await swapRouter.getAddress()); - }); - it('should set params', async () => { - const defaultParams = getDefaultSwapParams(await inputToken.getAddress(), await outputToken.getAddress()); - const params = await l2TokenReceiver.secondSwapParams(); - - expect(params.tokenIn).to.equal(defaultParams.tokenIn); - expect(params.tokenOut).to.equal(defaultParams.tokenOut); - expect(params.fee).to.equal(defaultParams.fee); - expect(params.sqrtPriceLimitX96).to.equal(defaultParams.sqrtPriceLimitX96); - }); - it('should give allowance', async () => { - expect(await inputToken.allowance(l2TokenReceiver, swapRouter)).to.equal(ethers.MaxUint256); - expect(await inputToken.allowance(l2TokenReceiver, nonfungiblePositionManager)).to.equal(ethers.MaxUint256); - expect(await outputToken.allowance(l2TokenReceiver, nonfungiblePositionManager)).to.equal(ethers.MaxUint256); - }); - }); - - describe('#_authorizeUpgrade', () => { - it('should correctly upgrade', async () => { - const l2TokenReceiverV2Factory = await ethers.getContractFactory('L2TokenReceiverV2'); - const l2TokenReceiverV2Implementation = await l2TokenReceiverV2Factory.deploy(); - - await l2TokenReceiver.upgradeTo(l2TokenReceiverV2Implementation); - - const l2TokenReceiverV2 = l2TokenReceiverV2Factory.attach(l2TokenReceiver) as L2TokenReceiverV2; - - expect(await l2TokenReceiverV2.version()).to.eq(2); - expect(await l2TokenReceiverV2.router()).to.eq(await swapRouter.getAddress()); - expect(await l2TokenReceiverV2.nonfungiblePositionManager()).to.eq( - await nonfungiblePositionManager.getAddress(), - ); - const secondSwapParams = await l2TokenReceiverV2.secondSwapParams(); - expect(secondSwapParams.tokenIn).to.eq(await inputToken.getAddress()); - expect(secondSwapParams.tokenOut).to.eq(await outputToken.getAddress()); - expect(secondSwapParams.fee).to.eq(500); - expect(secondSwapParams.sqrtPriceLimitX96).to.eq(0); - const firstSwapParams = await l2TokenReceiverV2.firstSwapParams(); - expect(firstSwapParams.tokenIn).to.eq(ZERO_ADDR); - expect(firstSwapParams.tokenOut).to.eq(ZERO_ADDR); - expect(firstSwapParams.fee).to.eq(0); - expect(firstSwapParams.sqrtPriceLimitX96).to.eq(0); - }); - it('should revert if caller is not the owner', async () => { - await expect(l2TokenReceiver.connect(SECOND).upgradeTo(ZERO_ADDR)).to.be.revertedWith( - 'Ownable: caller is not the owner', - ); - }); - }); - }); - - describe('supportsInterface', () => { - it('should support IL2TokenReceiver', async () => { - expect(await l2TokenReceiver.supportsInterface('0x2f958df3')).to.be.true; - }); - it('should support IERC165', async () => { - expect(await l2TokenReceiver.supportsInterface('0x01ffc9a7')).to.be.true; - }); - it('should support IERC721Receiver', async () => { - expect(await l2TokenReceiver.supportsInterface('0x150b7a02')).to.be.true; - }); - }); - - describe('#editParams', () => { - it('should edit params', async () => { - const newParams: IL2TokenReceiverV2.SwapParamsStruct = { - tokenIn: await outputToken.getAddress(), - tokenOut: await inputToken.getAddress(), - fee: 1, - sqrtPriceLimitX96: 1, - }; - - await l2TokenReceiver.editParams(newParams, false); - - const params = await l2TokenReceiver.secondSwapParams(); - - expect(params.tokenIn).to.equal(newParams.tokenIn); - expect(params.tokenOut).to.equal(newParams.tokenOut); - expect(params.fee).to.equal(newParams.fee); - expect(params.sqrtPriceLimitX96).to.equal(newParams.sqrtPriceLimitX96); - }); - it('should set new allowance', async () => { - const newParams: IL2TokenReceiverV2.SwapParamsStruct = { - tokenIn: await outputToken.getAddress(), - tokenOut: await inputToken.getAddress(), - fee: 1, - sqrtPriceLimitX96: 1, - }; - - await l2TokenReceiver.editParams(newParams, false); - - expect(await inputToken.allowance(l2TokenReceiver, swapRouter)).to.equal(0); - expect(await inputToken.allowance(l2TokenReceiver, nonfungiblePositionManager)).to.equal(ethers.MaxUint256); - expect(await outputToken.allowance(l2TokenReceiver, swapRouter)).to.equal(ethers.MaxUint256); - expect(await outputToken.allowance(l2TokenReceiver, nonfungiblePositionManager)).to.equal(ethers.MaxUint256); - }); - it('should revert if caller is not owner', async () => { - await expect( - l2TokenReceiver.connect(SECOND).editParams(getDefaultSwapParams(ZERO_ADDR, ZERO_ADDR), false), - ).to.be.revertedWith('Ownable: caller is not the owner'); - }); - it('should revert if tokenIn is zero address', async () => { - await expect( - l2TokenReceiver.editParams(getDefaultSwapParams(ZERO_ADDR, await outputToken.getAddress()), false), - ).to.be.revertedWith('L2TR: invalid tokenIn'); - }); - it('should revert if tokenOut is zero address', async () => { - await expect( - l2TokenReceiver.editParams(getDefaultSwapParams(await inputToken.getAddress(), ZERO_ADDR), false), - ).to.be.revertedWith('L2TR: invalid tokenOut'); - }); - }); - - describe('#swap', () => { - it('should return if caller is not the owner', async () => { - await expect(l2TokenReceiver.connect(SECOND).swap(1, 1, 1, false)).to.be.revertedWith( - 'Ownable: caller is not the owner', - ); - }); - }); - - describe('#increaseLiquidityCurrentRange', () => { - it('should return if caller is not the owner', async () => { - await expect(l2TokenReceiver.connect(SECOND).increaseLiquidityCurrentRange(1, 1, 1, 0, 0)).to.be.revertedWith( - 'Ownable: caller is not the owner', - ); - }); - }); - - describe('#withdrawToken', () => { - it('should withdraw token', async () => { - await inputToken.mint(l2TokenReceiver, 1); - - const tx = await l2TokenReceiver.withdrawToken(OWNER, inputToken, 1); - - await expect(tx).to.changeTokenBalances(inputToken, [l2TokenReceiver, OWNER], [-1, 1]); - }); - it('should return if caller is not the owner', async () => { - await expect(l2TokenReceiver.connect(SECOND).withdrawToken(OWNER, inputToken, 1)).to.be.revertedWith( - 'Ownable: caller is not the owner', - ); - }); - }); - - describe('#withdrawTokenId', () => { - it('should return if caller is not the owner', async () => { - await expect(l2TokenReceiver.connect(SECOND).withdrawTokenId(OWNER, OWNER, 0)).to.be.revertedWith( - 'Ownable: caller is not the owner', - ); - }); - }); -}); - -// npx hardhat test "test/L2TokenReceiverV2.test.ts" diff --git a/test/L2/MOR20.test.ts b/test/L2/MOR20.test.ts new file mode 100644 index 0000000..f718cd2 --- /dev/null +++ b/test/L2/MOR20.test.ts @@ -0,0 +1,130 @@ +import { SignerWithAddress } from '@nomicfoundation/hardhat-ethers/signers'; +import { expect } from 'chai'; +import { ethers } from 'hardhat'; + +import { LayerZeroEndpointV2Mock, MOR20 } from '@/generated-types/ethers'; +import { ZERO_ADDR } from '@/scripts/utils/constants'; +import { wei } from '@/scripts/utils/utils'; +import { Reverter } from '@/test/helpers/reverter'; + +describe('MOR20', () => { + const reverter = new Reverter(); + + let OWNER: SignerWithAddress; + let SECOND: SignerWithAddress; + let MINTER: SignerWithAddress; + let DELEGATE: SignerWithAddress; + let LZ_ENDPOINT_OWNER: SignerWithAddress; + + let rewardToken: MOR20; + let lZEndpointMock: LayerZeroEndpointV2Mock; + + const chainId = 101; + + before(async () => { + [OWNER, SECOND, MINTER, DELEGATE, LZ_ENDPOINT_OWNER] = await ethers.getSigners(); + + const [LZEndpointMock, MOR20] = await Promise.all([ + ethers.getContractFactory('LayerZeroEndpointV2Mock'), + ethers.getContractFactory('MOR20'), + ]); + + lZEndpointMock = await LZEndpointMock.deploy(chainId, LZ_ENDPOINT_OWNER); + rewardToken = await MOR20.deploy('TEST', 'TST', lZEndpointMock, DELEGATE, MINTER); + + await reverter.snapshot(); + }); + + afterEach(async () => { + await reverter.revert(); + }); + + describe('constructor', () => { + it('should set the name and symbol', async () => { + expect(await rewardToken.name()).to.equal('TEST'); + expect(await rewardToken.symbol()).to.equal('TST'); + expect(await rewardToken.isMinter(MINTER)).to.be.true; + }); + it('should revert if LZ endpoint is zero address', async () => { + const MOR20 = await ethers.getContractFactory('MOR20'); + + await expect(MOR20.deploy('TEST', 'TST', lZEndpointMock, DELEGATE.address, ZERO_ADDR)).to.be.revertedWith( + 'MOR20: invalid minter', + ); + }); + }); + + describe('supportsInterface', () => { + it('should support IToken', async () => { + expect(await rewardToken.supportsInterface('0x38f90a90')).to.be.true; + }); + it('should support IERC20', async () => { + expect(await rewardToken.supportsInterface('0x36372b07')).to.be.true; + }); + it('should support IOAppCore', async () => { + expect(await rewardToken.supportsInterface('0x0c39d358')).to.be.true; + }); + it('should support IERC165', async () => { + expect(await rewardToken.supportsInterface('0x01ffc9a7')).to.be.true; + }); + }); + + describe('mint', () => { + it('should mint tokens', async () => { + const amount = wei('10'); + + const tx = await rewardToken.connect(MINTER).mint(SECOND.address, amount); + await expect(tx).to.changeTokenBalance(rewardToken, SECOND, amount); + }); + it('should revert if not called by the owner', async () => { + await expect(rewardToken.connect(SECOND).mint(SECOND.address, wei('10'))).to.be.revertedWith( + 'MOR20: invalid caller', + ); + }); + }); + + describe('#updateMinter', () => { + it('should update the minter', async () => { + await rewardToken.connect(DELEGATE).updateMinter(SECOND, true); + + expect(await rewardToken.isMinter(SECOND)).to.be.true; + + await rewardToken.connect(DELEGATE).updateMinter(SECOND, false); + + expect(await rewardToken.isMinter(SECOND)).to.be.false; + }); + it('should revert if not called by the owner', async () => { + await expect(rewardToken.connect(SECOND).updateMinter(SECOND, true)).to.be.revertedWith( + 'Ownable: caller is not the owner', + ); + }); + }); + + describe('burn', () => { + it('should burn tokens', async () => { + const amount = wei('10'); + + await rewardToken.connect(MINTER).mint(OWNER.address, amount); + + const tx = await rewardToken.burn(amount); + + await expect(tx).to.changeTokenBalance(rewardToken, OWNER, -amount); + }); + }); + + describe('burnFrom', () => { + it('should burn tokens from another account', async () => { + const amount = wei('10'); + + await rewardToken.connect(MINTER).mint(OWNER.address, amount); + + await rewardToken.approve(SECOND.address, amount); + + const tx = await rewardToken.connect(SECOND).burnFrom(OWNER.address, amount); + + await expect(tx).to.changeTokenBalance(rewardToken, OWNER, -amount); + + expect(await rewardToken.allowance(OWNER.address, SECOND.address)).to.equal(0); + }); + }); +}); diff --git a/test/L2/MOROFT.test.ts b/test/L2/MOROFT.test.ts deleted file mode 100644 index 7bfb458..0000000 --- a/test/L2/MOROFT.test.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { SignerWithAddress } from '@nomicfoundation/hardhat-ethers/signers'; -import { ethers, expect } from 'hardhat'; - -import { LayerZeroEndpointV2Mock, MOROFT } from '@/generated-types/ethers'; -import { ZERO_ADDR } from '@/scripts/utils/constants'; -import { wei } from '@/scripts/utils/utils'; -import { Reverter } from '@/test/helpers/reverter'; - -describe('MOROFT', () => { - const reverter = new Reverter(); - - let OWNER: SignerWithAddress; - let SECOND: SignerWithAddress; - let MINTER: SignerWithAddress; - let DELEGATE: SignerWithAddress; - let LZ_ENDPOINT_OWNER: SignerWithAddress; - - let mor: MOROFT; - let lZEndpointMock: LayerZeroEndpointV2Mock; - - const chainId = 101; - - before(async () => { - [OWNER, SECOND, MINTER, DELEGATE, LZ_ENDPOINT_OWNER] = await ethers.getSigners(); - - const [LZEndpointMock, MOR] = await Promise.all([ - ethers.getContractFactory('LayerZeroEndpointV2Mock'), - ethers.getContractFactory('MOROFT'), - ]); - - lZEndpointMock = await LZEndpointMock.deploy(chainId, LZ_ENDPOINT_OWNER.address); - mor = await MOR.deploy(lZEndpointMock, DELEGATE.address, MINTER.address); - - await reverter.snapshot(); - }); - - afterEach(async () => { - await reverter.revert(); - }); - - describe('constructor', () => { - it('should set the name and symbol', async () => { - expect(await mor.name()).to.equal('MOR'); - expect(await mor.symbol()).to.equal('MOR'); - expect(await mor.minter()).to.equal(MINTER.address); - }); - it('should revert if LZ endpoint is zero address', async () => { - const MOR = await ethers.getContractFactory('MOROFT'); - - await expect(MOR.deploy(lZEndpointMock, DELEGATE.address, ZERO_ADDR)).to.be.revertedWith( - 'MOROFT: invalid minter', - ); - }); - }); - - describe('supportsInterface', () => { - it('should support IMOROFT', async () => { - expect(await mor.supportsInterface('0x7ccf6593')).to.be.true; - }); - it('should support IERC20', async () => { - expect(await mor.supportsInterface('0x36372b07')).to.be.true; - }); - it('should support IOAppCore', async () => { - expect(await mor.supportsInterface('0x0c39d358')).to.be.true; - }); - it('should support IERC165', async () => { - expect(await mor.supportsInterface('0x01ffc9a7')).to.be.true; - }); - }); - - describe('mint', () => { - it('should mint tokens', async () => { - const amount = wei('10'); - - const tx = await mor.connect(MINTER).mint(SECOND.address, amount); - await expect(tx).to.changeTokenBalance(mor, SECOND, amount); - }); - it('should revert if not called by the owner', async () => { - await expect(mor.connect(SECOND).mint(SECOND.address, wei('10'))).to.be.revertedWith('MOROFT: invalid caller'); - }); - }); - - describe('burn', () => { - it('should burn tokens', async () => { - const amount = wei('10'); - - await mor.connect(MINTER).mint(OWNER.address, amount); - - const tx = await mor.burn(amount); - - await expect(tx).to.changeTokenBalance(mor, OWNER, -amount); - }); - }); - - describe('burnFrom', () => { - it('should burn tokens from another account', async () => { - const amount = wei('10'); - - await mor.connect(MINTER).mint(OWNER.address, amount); - - await mor.approve(SECOND.address, amount); - - const tx = await mor.connect(SECOND).burnFrom(OWNER.address, amount); - - await expect(tx).to.changeTokenBalance(mor, OWNER, -amount); - - expect(await mor.allowance(OWNER.address, SECOND.address)).to.equal(0); - }); - }); -}); - -// npx hardhat test "test/MOROFT.test.ts" -// npx hardhat coverage --solcoverjs ./.solcover.ts --testfiles "test/MOROFT.test.ts" diff --git a/test/fork/L1Sender.fork.test.ts b/test/fork/L1Sender.fork.test.ts index 67cafa9..121bdb9 100644 --- a/test/fork/L1Sender.fork.test.ts +++ b/test/fork/L1Sender.fork.test.ts @@ -39,7 +39,7 @@ describe('L1Sender Fork', () => { await ethers.provider.send('hardhat_reset', [ { forking: { - jsonRpcUrl: `https://mainnet.infura.io/v3/${process.env.INFURA_KEY}`, + jsonRpcUrl: `https://eth-mainnet.g.alchemy.com/v2/${process.env.ALCHEMY_KEY}`, }, }, ]); diff --git a/test/fork/L2TokenReceiver.fork.test.ts b/test/fork/L2TokenReceiver.fork.test.ts index 61d496e..96ec7a6 100644 --- a/test/fork/L2TokenReceiver.fork.test.ts +++ b/test/fork/L2TokenReceiver.fork.test.ts @@ -11,9 +11,8 @@ import { IL2TokenReceiver, INonfungiblePositionManager, INonfungiblePositionManager__factory, - ISwapRouter, - ISwapRouter__factory, L2TokenReceiver, + MOR20, WStETHMock, WStETHMock__factory, } from '@/generated-types/ethers'; @@ -23,55 +22,108 @@ describe('L2TokenReceiver Fork', () => { const reverter = new Reverter(); let OWNER: SignerWithAddress; + let SECOND: SignerWithAddress; let l2TokenReceiver: L2TokenReceiver; - const swapRouterAddress = '0xE592427A0AEce92De3Edee1F18E0157C05861564'; + const router = '0xE592427A0AEce92De3Edee1F18E0157C05861564'; const nonfungiblePositionManagerAddress = '0xC36442b4a4522E871399CD717aBDD847Ab11FE88'; - const wstethAddress = '0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0'; - const usdcAddress = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'; + const l1LzEndpointV2Address = '0x1a44076050125825900e736c501f859c50fe728c'; - const richAddress = '0x176F3DAb24a159341c0509bB36B833E7fdd0a132'; + const wethAddress = '0x82aF49447D8a07e3bd95BD0d56f35241523fBab1'; + + const richAddress = '0xE74546162c7c58929b898575C378Fd7EC5B16998'; - let swapRouter: ISwapRouter; let nonfungiblePositionManager: INonfungiblePositionManager; let inputToken: WStETHMock; - let outputToken: IERC20; + let innerToken: IERC20; + let outputToken: MOR20; + + let poolId: bigint; before(async () => { await ethers.provider.send('hardhat_reset', [ { forking: { - jsonRpcUrl: `https://mainnet.infura.io/v3/${process.env.INFURA_KEY}`, - blockNumber: 19000000, + jsonRpcUrl: `https://arb-mainnet.g.alchemy.com/v2/${process.env.ALCHEMY_KEY}`, + blockNumber: 189500000, }, }, ]); OWNER = await ethers.getImpersonatedSigner(richAddress); + [SECOND] = await ethers.getSigners(); - swapRouter = ISwapRouter__factory.connect(swapRouterAddress, OWNER); - nonfungiblePositionManager = INonfungiblePositionManager__factory.connect(nonfungiblePositionManagerAddress, OWNER); + await SECOND.sendTransaction({ to: richAddress, value: wei(100) }); - inputToken = WStETHMock__factory.connect(wstethAddress, OWNER); - outputToken = IERC20__factory.connect(usdcAddress, OWNER); + nonfungiblePositionManager = INonfungiblePositionManager__factory.connect(nonfungiblePositionManagerAddress, OWNER); - const [ERC1967ProxyFactory, L2TokenReceiver] = await Promise.all([ - ethers.getContractFactory('ERC1967Proxy', OWNER), + const [L2TokenReceiver, MOR, ERC1967ProxyFactory] = await Promise.all([ ethers.getContractFactory('L2TokenReceiver', OWNER), + ethers.getContractFactory('MOR20', OWNER), + ethers.getContractFactory('ERC1967Proxy', OWNER), ]); - const l2TokenReceiverImplementation = await L2TokenReceiver.deploy(); - const l2TokenReceiverProxy = await ERC1967ProxyFactory.deploy(l2TokenReceiverImplementation, '0x'); + const l2TokenReceiverImpl = await L2TokenReceiver.deploy(); + const l2TokenReceiverProxy = await ERC1967ProxyFactory.deploy(l2TokenReceiverImpl, '0x'); l2TokenReceiver = L2TokenReceiver.attach(l2TokenReceiverProxy) as L2TokenReceiver; + + innerToken = IERC20__factory.connect(wethAddress, OWNER); + inputToken = WStETHMock__factory.connect('0x5979D7b546E38E414F7E9822514be443A4800529', OWNER); + outputToken = (await MOR.deploy('MOR20', 'MOR20', l1LzEndpointV2Address, OWNER, OWNER)).connect(OWNER); + await l2TokenReceiver.L2TokenReceiver__init( - swapRouter, + router, nonfungiblePositionManager, - getDefaultSwapParams(await inputToken.getAddress(), await outputToken.getAddress()), + getDefaultSwapParams(await inputToken.getAddress(), await innerToken.getAddress()), + getDefaultSwapParams(await innerToken.getAddress(), await outputToken.getAddress()), ); + await outputToken.mint(OWNER, wei(1000)); + + // Create a pool + + await innerToken.approve(nonfungiblePositionManagerAddress, wei(1000)); + await outputToken.approve(nonfungiblePositionManagerAddress, wei(1000)); + + const sqrtPrice = 2505413655765166104103837312489n; + + await nonfungiblePositionManager.createAndInitializePoolIfNecessary(innerToken, outputToken, 500, sqrtPrice); + + poolId = ( + await nonfungiblePositionManager.mint.staticCall({ + token0: innerToken, + token1: outputToken, + fee: 500, + tickLower: -887220, + tickUpper: 887220, + amount0Desired: wei(0.01), + amount1Desired: 9999993390433544889n, + amount0Min: 0, + amount1Min: 0, + recipient: OWNER, + deadline: (await getCurrentBlockTime()) + 100, + }) + ).tokenId; + + await nonfungiblePositionManager.mint({ + token0: innerToken, + token1: outputToken, + fee: 500, + tickLower: -887220, + tickUpper: 887220, + amount0Desired: wei(0.01), + amount1Desired: 9999993390433544889n, + amount0Min: 0, + amount1Min: 0, + recipient: OWNER, + deadline: (await getCurrentBlockTime()) + 100, + }); + + await nonfungiblePositionManager['safeTransferFrom(address,address,uint256)'](OWNER, l2TokenReceiver, poolId); + await reverter.snapshot(); }); @@ -84,28 +136,34 @@ describe('L2TokenReceiver Fork', () => { }); describe('#swap', () => { - const amount = wei(0.0001); + const amount = wei(0.00001); beforeEach('setup', async () => { await inputToken.transfer(l2TokenReceiver, amount); + await innerToken.transfer(l2TokenReceiver, amount); }); - it('should swap tokens', async () => { - const txResult = await l2TokenReceiver.swap.staticCall(amount, wei(0), (await getCurrentBlockTime()) + 100); - const tx = await l2TokenReceiver.swap(amount, wei(0), (await getCurrentBlockTime()) + 100); + it('should swap tokens 1', async () => { + const txResult = await l2TokenReceiver.swap.staticCall(amount, 0, (await getCurrentBlockTime()) + 100, 0, true); + const tx = await l2TokenReceiver.swap(amount, 0, (await getCurrentBlockTime()) + 100, 0, true); - await expect(tx).to.changeTokenBalance(outputToken, l2TokenReceiver, txResult); + await expect(tx).to.changeTokenBalance(innerToken, l2TokenReceiver, txResult); await expect(tx).to.changeTokenBalance(inputToken, l2TokenReceiver, -amount); }); + it('should swap tokens 2', async () => { + const txResult = await l2TokenReceiver.swap.staticCall(amount, 0, (await getCurrentBlockTime()) + 100, 0, false); + const tx = await l2TokenReceiver.swap(amount, 0, (await getCurrentBlockTime()) + 100, 0, false); + + await expect(tx).to.changeTokenBalance(outputToken, l2TokenReceiver, txResult); + await expect(tx).to.changeTokenBalance(innerToken, l2TokenReceiver, -amount); + }); }); describe('#increaseLiquidityCurrentRange', () => { - const amountInputToken = wei(0.0001); - const amountOutputToken = 541774411822; + const amountInputToken = wei(0.00001); + const amountOutputToken = wei(0.1); - // const poolId = '0x4622df6fb2d9bee0dcdacf545acdb6a2b2f4f863'; - const poolId = 376582; beforeEach('setup', async () => { - await inputToken.transfer(l2TokenReceiver, amountInputToken); + await innerToken.transfer(l2TokenReceiver, amountInputToken); await outputToken.transfer(l2TokenReceiver, amountOutputToken); }); @@ -121,17 +179,16 @@ describe('L2TokenReceiver Fork', () => { const tx = await l2TokenReceiver.increaseLiquidityCurrentRange(poolId, amountInputToken, amountOutputToken, 0, 0); await expect(tx).to.changeTokenBalance(outputToken, l2TokenReceiver, -txResult[2]); - await expect(tx).to.changeTokenBalance(inputToken, l2TokenReceiver, -txResult[1]); + await expect(tx).to.changeTokenBalance(innerToken, l2TokenReceiver, -txResult[1]); }); it('should set the amount correctly besides the tokens order', async () => { const newParams: IL2TokenReceiver.SwapParamsStruct = { tokenIn: await outputToken.getAddress(), - tokenOut: await inputToken.getAddress(), + tokenOut: await innerToken.getAddress(), fee: 1, - sqrtPriceLimitX96: 1, }; - await l2TokenReceiver.editParams(newParams); + await l2TokenReceiver.editParams(newParams, false); const txResult = await l2TokenReceiver.increaseLiquidityCurrentRange.staticCall( poolId, @@ -142,35 +199,95 @@ describe('L2TokenReceiver Fork', () => { ); const tx = await l2TokenReceiver.increaseLiquidityCurrentRange(poolId, amountInputToken, amountOutputToken, 0, 0); - await expect(tx).to.changeTokenBalance(inputToken, l2TokenReceiver, -txResult[1]); + await expect(tx).to.changeTokenBalance(innerToken, l2TokenReceiver, -txResult[1]); await expect(tx).to.changeTokenBalance(outputToken, l2TokenReceiver, -txResult[2]); }); }); describe('#collectFees', () => { - const poolId = 376582; - beforeEach('setup', async () => { - const poolOwner = await nonfungiblePositionManager.ownerOf(poolId); - const poolOwnerSigner = await ethers.getImpersonatedSigner(poolOwner); - - await OWNER.sendTransaction({ to: poolOwner, value: wei(10) }); + await innerToken.transfer(l2TokenReceiver, wei(0.001)); - await nonfungiblePositionManager - .connect(poolOwnerSigner) - ['safeTransferFrom(address,address,uint256)'](poolOwner, l2TokenReceiver, poolId); + await l2TokenReceiver.swap(wei(0.0001), 0, (await getCurrentBlockTime()) + 100, 0, false); }); it('should collect fees', async () => { const outputTokenBalance = await outputToken.balanceOf(l2TokenReceiver); - const inputTokenBalance = await inputToken.balanceOf(l2TokenReceiver); + const inputTokenBalance = await innerToken.balanceOf(l2TokenReceiver); await l2TokenReceiver.collectFees(poolId); - expect(await outputToken.balanceOf(l2TokenReceiver)).to.greaterThan(outputTokenBalance); - expect(await inputToken.balanceOf(l2TokenReceiver)).to.greaterThan(inputTokenBalance); + expect(await outputToken.balanceOf(l2TokenReceiver)).to.be.equal(outputTokenBalance); + expect(await innerToken.balanceOf(l2TokenReceiver)).to.be.greaterThan(inputTokenBalance); + }); + }); + + describe('#decreaseLiquidityCurrentRange', () => { + const amountInputToken = wei(0.00001); + const amountOutputToken = wei(0.1); + + beforeEach('setup', async () => { + await innerToken.transfer(l2TokenReceiver, amountInputToken); + await outputToken.transfer(l2TokenReceiver, amountOutputToken); + }); + + it('should descrease liquidity', async () => { + const liquidity = (await nonfungiblePositionManager.positions(poolId)).liquidity; + + const txResult = await l2TokenReceiver.decreaseLiquidityCurrentRange.staticCall( + poolId, + liquidity, + amountInputToken, + amountOutputToken, + ); + + const tx = await l2TokenReceiver.decreaseLiquidityCurrentRange( + poolId, + liquidity, + amountInputToken, + amountOutputToken, + ); + + await expect(tx).to.changeTokenBalance(outputToken, l2TokenReceiver, txResult[1]); + await expect(tx).to.changeTokenBalance(innerToken, l2TokenReceiver, txResult[0]); + }); + it('should collect fees', async () => { + await innerToken.transfer(l2TokenReceiver, wei(0.001)); + + await l2TokenReceiver.swap(wei(0.0001), 0, (await getCurrentBlockTime()) + 100, 0, false); + + const liquidity = (await nonfungiblePositionManager.positions(poolId)).liquidity; + + const feeResult = await l2TokenReceiver.collectFees.staticCall(poolId); + + const txResult = await l2TokenReceiver.decreaseLiquidityCurrentRange.staticCall( + poolId, + liquidity, + amountInputToken, + amountOutputToken, + ); + + const tx = await l2TokenReceiver.decreaseLiquidityCurrentRange( + poolId, + liquidity, + amountInputToken, + amountOutputToken, + ); + + await expect(tx).to.changeTokenBalance(outputToken, l2TokenReceiver, txResult[1] + feeResult[1]); + await expect(tx).to.changeTokenBalance(innerToken, l2TokenReceiver, txResult[0] + feeResult[0]); + }); + }); + + describe('#withdrawTokenId', () => { + it('should withdraw position NFT', async () => { + expect(await nonfungiblePositionManager.ownerOf(poolId)).to.be.equal(await l2TokenReceiver.getAddress()); + + await l2TokenReceiver.withdrawTokenId(OWNER, nonfungiblePositionManager, poolId); + + expect(await nonfungiblePositionManager.ownerOf(poolId)).to.be.equal(await OWNER.getAddress()); }); }); }); -// npx hardhat test "test/fork/L2TokenReceiver.fork.test.ts" +// npx hardhat test "test/fork/L2TokenReceiverV2.fork.test.ts" diff --git a/test/fork/L2TokenReceiverV2.fork.test.ts b/test/fork/L2TokenReceiverV2.fork.test.ts deleted file mode 100644 index 092a40f..0000000 --- a/test/fork/L2TokenReceiverV2.fork.test.ts +++ /dev/null @@ -1,247 +0,0 @@ -import { SignerWithAddress } from '@nomicfoundation/hardhat-ethers/signers'; -import { assert } from 'console'; -import { ethers, expect } from 'hardhat'; - -import { getCurrentBlockTime } from '../helpers/block-helper'; -import { getDefaultSwapParams } from '../helpers/distribution-helper'; -import { Reverter } from '../helpers/reverter'; - -import { - IERC20, - IERC20__factory, - IL2TokenReceiver, - INonfungiblePositionManager, - INonfungiblePositionManager__factory, - L2TokenReceiverV2, - MOROFT, - WStETHMock, - WStETHMock__factory, -} from '@/generated-types/ethers'; -import { wei } from '@/scripts/utils/utils'; - -describe('L2TokenReceiverV2 Fork', () => { - const reverter = new Reverter(); - - let OWNER: SignerWithAddress; - let SECOND: SignerWithAddress; - - let l2TokenReceiver: L2TokenReceiverV2; - - const nonfungiblePositionManagerAddress = '0xC36442b4a4522E871399CD717aBDD847Ab11FE88'; - - const l1LzEndpointV2Address = '0x1a44076050125825900e736c501f859c50fe728c'; - - const wethAddress = '0x82aF49447D8a07e3bd95BD0d56f35241523fBab1'; - - const richAddress = '0xE74546162c7c58929b898575C378Fd7EC5B16998'; - - let nonfungiblePositionManager: INonfungiblePositionManager; - - let inputToken: WStETHMock; - let innerToken: IERC20; - let outputToken: MOROFT; - - let poolId: bigint; - - before(async () => { - await ethers.provider.send('hardhat_reset', [ - { - forking: { - jsonRpcUrl: `https://arbitrum-mainnet.infura.io/v3/${process.env.INFURA_KEY}`, - blockNumber: 189500000, - }, - }, - ]); - - OWNER = await ethers.getImpersonatedSigner(richAddress); - [SECOND] = await ethers.getSigners(); - - await SECOND.sendTransaction({ to: richAddress, value: wei(100) }); - - nonfungiblePositionManager = INonfungiblePositionManager__factory.connect(nonfungiblePositionManagerAddress, OWNER); - - const [L2TokenReceiverV2, MOR] = await Promise.all([ - ethers.getContractFactory('L2TokenReceiverV2', OWNER), - ethers.getContractFactory('MOROFT', OWNER), - ]); - - l2TokenReceiver = L2TokenReceiverV2.attach('0x47176B2Af9885dC6C4575d4eFd63895f7Aaa4790') as L2TokenReceiverV2; - - // Upgrade to V2 - const contractOwner = await ethers.getImpersonatedSigner(await l2TokenReceiver.owner()); - await SECOND.sendTransaction({ to: contractOwner, value: wei(100) }); - await l2TokenReceiver.connect(contractOwner).transferOwnership(OWNER); - - const l2TokenReceiverImplementationV2 = await L2TokenReceiverV2.deploy(); - await l2TokenReceiver.upgradeTo(l2TokenReceiverImplementationV2); - - l2TokenReceiver = L2TokenReceiverV2.attach(l2TokenReceiver) as L2TokenReceiverV2; - - innerToken = IERC20__factory.connect(wethAddress, OWNER); - inputToken = WStETHMock__factory.connect((await l2TokenReceiver.secondSwapParams()).tokenIn, OWNER); - outputToken = (await MOR.deploy(l1LzEndpointV2Address, OWNER, OWNER)).connect(OWNER); - - await outputToken.mint(OWNER, wei(1000)); - - await l2TokenReceiver.editParams( - getDefaultSwapParams(await innerToken.getAddress(), await outputToken.getAddress()), - false, - ); - await l2TokenReceiver.editParams( - getDefaultSwapParams(await inputToken.getAddress(), await innerToken.getAddress()), - true, - ); - - // Create a pool - - await innerToken.approve(nonfungiblePositionManagerAddress, wei(1000)); - await outputToken.approve(nonfungiblePositionManagerAddress, wei(1000)); - - const sqrtPrice = 2505413655765166104103837312489n; - - await nonfungiblePositionManager.createAndInitializePoolIfNecessary(innerToken, outputToken, 500, sqrtPrice); - - poolId = ( - await nonfungiblePositionManager.mint.staticCall({ - token0: innerToken, - token1: outputToken, - fee: 500, - tickLower: -887220, - tickUpper: 887220, - amount0Desired: wei(0.01), - amount1Desired: 9999993390433544889n, - amount0Min: 0, - amount1Min: 0, - recipient: OWNER, - deadline: (await getCurrentBlockTime()) + 100, - }) - ).tokenId; - - await nonfungiblePositionManager.mint({ - token0: innerToken, - token1: outputToken, - fee: 500, - tickLower: -887220, - tickUpper: 887220, - amount0Desired: wei(0.01), - amount1Desired: 9999993390433544889n, - amount0Min: 0, - amount1Min: 0, - recipient: OWNER, - deadline: (await getCurrentBlockTime()) + 100, - }); - - await nonfungiblePositionManager['safeTransferFrom(address,address,uint256)'](OWNER, l2TokenReceiver, poolId); - assert((await l2TokenReceiver.version()) === 2n, 'L2TokenReceiver should be upgraded to V2'); - - await reverter.snapshot(); - }); - - beforeEach(async () => { - await reverter.revert(); - }); - - after(async () => { - await ethers.provider.send('hardhat_reset', []); - }); - - describe('#swap', () => { - const amount = wei(0.00001); - beforeEach('setup', async () => { - await inputToken.transfer(l2TokenReceiver, amount); - await innerToken.transfer(l2TokenReceiver, amount); - }); - - it('should swap tokens 1', async () => { - const txResult = await l2TokenReceiver.swap.staticCall(amount, 0, (await getCurrentBlockTime()) + 100, true); - const tx = await l2TokenReceiver.swap(amount, 0, (await getCurrentBlockTime()) + 100, true); - - await expect(tx).to.changeTokenBalance(innerToken, l2TokenReceiver, txResult); - await expect(tx).to.changeTokenBalance(inputToken, l2TokenReceiver, -amount); - }); - it('should swap tokens 2', async () => { - const txResult = await l2TokenReceiver.swap.staticCall(amount, 0, (await getCurrentBlockTime()) + 100, false); - const tx = await l2TokenReceiver.swap(amount, 0, (await getCurrentBlockTime()) + 100, false); - - await expect(tx).to.changeTokenBalance(outputToken, l2TokenReceiver, txResult); - await expect(tx).to.changeTokenBalance(innerToken, l2TokenReceiver, -amount); - }); - }); - - describe('#increaseLiquidityCurrentRange', () => { - const amountInputToken = wei(0.00001); - const amountOutputToken = wei(0.1); - - beforeEach('setup', async () => { - await innerToken.transfer(l2TokenReceiver, amountInputToken); - await outputToken.transfer(l2TokenReceiver, amountOutputToken); - }); - - it('should increase liquidity', async () => { - const txResult = await l2TokenReceiver.increaseLiquidityCurrentRange.staticCall( - poolId, - amountInputToken, - amountOutputToken, - 0, - 0, - ); - - const tx = await l2TokenReceiver.increaseLiquidityCurrentRange(poolId, amountInputToken, amountOutputToken, 0, 0); - - await expect(tx).to.changeTokenBalance(outputToken, l2TokenReceiver, -txResult[2]); - await expect(tx).to.changeTokenBalance(innerToken, l2TokenReceiver, -txResult[1]); - }); - it('should set the amount correctly besides the tokens order', async () => { - const newParams: IL2TokenReceiver.SwapParamsStruct = { - tokenIn: await outputToken.getAddress(), - tokenOut: await innerToken.getAddress(), - fee: 1, - sqrtPriceLimitX96: 1, - }; - - await l2TokenReceiver.editParams(newParams, false); - - const txResult = await l2TokenReceiver.increaseLiquidityCurrentRange.staticCall( - poolId, - amountInputToken, - amountOutputToken, - 0, - 0, - ); - const tx = await l2TokenReceiver.increaseLiquidityCurrentRange(poolId, amountInputToken, amountOutputToken, 0, 0); - - await expect(tx).to.changeTokenBalance(innerToken, l2TokenReceiver, -txResult[1]); - await expect(tx).to.changeTokenBalance(outputToken, l2TokenReceiver, -txResult[2]); - }); - }); - - describe('#collectFees', () => { - beforeEach('setup', async () => { - await innerToken.transfer(l2TokenReceiver, wei(0.001)); - - await l2TokenReceiver.swap(wei(0.0001), 0, (await getCurrentBlockTime()) + 100, false); - }); - - it('should collect fees', async () => { - const outputTokenBalance = await outputToken.balanceOf(l2TokenReceiver); - const inputTokenBalance = await innerToken.balanceOf(l2TokenReceiver); - - await l2TokenReceiver.collectFees(poolId); - - expect(await outputToken.balanceOf(l2TokenReceiver)).to.be.equal(outputTokenBalance); - expect(await innerToken.balanceOf(l2TokenReceiver)).to.be.greaterThan(inputTokenBalance); - }); - }); - - describe('#withdrawTokenId', () => { - it('should withdraw position NFT', async () => { - expect(await nonfungiblePositionManager.ownerOf(poolId)).to.be.equal(await l2TokenReceiver.getAddress()); - - await l2TokenReceiver.withdrawTokenId(OWNER, nonfungiblePositionManager, poolId); - - expect(await nonfungiblePositionManager.ownerOf(poolId)).to.be.equal(await OWNER.getAddress()); - }); - }); -}); - -// npx hardhat test "test/fork/L2TokenReceiverV2.fork.test.ts" diff --git a/test/fork/MOROFT.fork.test.ts b/test/fork/MOR20.fork.test.ts similarity index 87% rename from test/fork/MOROFT.fork.test.ts rename to test/fork/MOR20.fork.test.ts index f272728..4b22866 100644 --- a/test/fork/MOROFT.fork.test.ts +++ b/test/fork/MOR20.fork.test.ts @@ -1,11 +1,11 @@ import { SignerWithAddress } from '@nomicfoundation/hardhat-ethers/signers'; import { ethers, expect } from 'hardhat'; -import { MOROFT, OptionsGenerator } from '@/generated-types/ethers'; +import { MOR20, OptionsGenerator } from '@/generated-types/ethers'; import { wei } from '@/scripts/utils/utils'; import { Reverter } from '@/test/helpers/reverter'; -describe('MOROFT', () => { +describe('MOR20 Fork', () => { const reverter = new Reverter(); let SECOND: SignerWithAddress; @@ -14,8 +14,8 @@ describe('MOROFT', () => { let optionsGenerator: OptionsGenerator; - let l1Mor: MOROFT; - let l2Mor: MOROFT; + let l1Mor: MOR20; + let l2Mor: MOR20; // *** LZ CONFIG *** // https://docs.layerzero.network/contracts/endpoint-addresses @@ -31,7 +31,7 @@ describe('MOROFT', () => { await ethers.provider.send('hardhat_reset', [ { forking: { - jsonRpcUrl: `https://arbitrum-mainnet.infura.io/v3/${process.env.INFURA_KEY}`, + jsonRpcUrl: `https://eth-mainnet.g.alchemy.com/v2/${process.env.ALCHEMY_KEY}`, // blockNumber: 190000000, }, }, @@ -40,13 +40,13 @@ describe('MOROFT', () => { [SECOND, MINTER, DELEGATE] = await ethers.getSigners(); const [MOR, OptionsGenerator] = await Promise.all([ - ethers.getContractFactory('MOROFT'), + ethers.getContractFactory('MOR20'), ethers.getContractFactory('OptionsGenerator'), ]); optionsGenerator = await OptionsGenerator.deploy(); - l1Mor = await MOR.deploy(l1LzEndpointV2Address, DELEGATE.address, MINTER.address); - l2Mor = await MOR.deploy(l2LzEndpointV2Address, DELEGATE.address, MINTER.address); + l1Mor = await MOR.deploy('MOR20_1', 'MOR20_1', l1LzEndpointV2Address, DELEGATE.address, MINTER.address); + l2Mor = await MOR.deploy('MOR20_2', 'MOR20_2', l2LzEndpointV2Address, DELEGATE.address, MINTER.address); await reverter.snapshot(); }); @@ -114,4 +114,4 @@ describe('MOROFT', () => { }); }); -// npx hardhat test "test/fork/MOROFT.fork.test.ts" +// npx hardhat test "test/fork/MOR20.fork.test.ts" diff --git a/test/helpers/distribution-helper.ts b/test/helpers/distribution-helper.ts index aa997b4..0833d14 100644 --- a/test/helpers/distribution-helper.ts +++ b/test/helpers/distribution-helper.ts @@ -23,6 +23,5 @@ export const getDefaultSwapParams = (tokenIn: string, tokenOut: string): IL2Toke tokenIn: tokenIn, tokenOut: tokenOut, fee: 500, - sqrtPriceLimitX96: 0, }; }; diff --git a/test/helpers/helper.ts b/test/helpers/helper.ts new file mode 100644 index 0000000..9d4d7da --- /dev/null +++ b/test/helpers/helper.ts @@ -0,0 +1,10 @@ +export enum PoolTypesL1 { + DISTRIBUTION, + L1_SENDER, +} + +export enum PoolTypesL2 { + L2_MESSAGE_RECEIVER, + L2_TOKEN_RECEIVER, + MOR20, +}