From b2115164af9855908fc74f7b1e3c22984ad5fffc Mon Sep 17 00:00:00 2001 From: Ruslan Kasheparov <55411970+RuslanProgrammer@users.noreply.github.com> Date: Fri, 12 Jan 2024 19:30:38 +0100 Subject: [PATCH] Dev (#14) * moved contracts to uups proxy * added tests * updated package.json * fixed issues * LZ nonblocking app (#5) * add nonblocking functionality lo LZ bridge * refactored --------- Co-authored-by: RuslanProgrammer * Cleanup files (#6) * refactored * added deploy all script * added config parser * fixed deploy all * fixed token bridge * fixed tests * Merge events into dev (#8) * Add events to contracts (#7) * added events * added tests for events * moved events to specific functions * added restriction for withdrawn after stake (#9) * Fixes after TestingReports review (#10) * fixed floating promises in tests * fixed 'No Conflicting Inheritance' * fixed 'unnecessarily permissive visibility' * fixed tests * fixed multiple issues * added mainnet config * added lint task (#11) * added lint task * changed order of ci tasks * StETH mock changes (#12) * Update StETHMock and WStETHMock contracts, and config files * Updated tests * fixed config * change implementation of StETH to almost real --------- Co-authored-by: David Johnston Co-authored-by: Oleksandr Fedorenko <104348310+FedokDL@users.noreply.github.com> --- .eslintrc.json | 6 +- .github/workflows/checks.yml | 4 +- contracts/Distribution.sol | 58 +- contracts/L1Sender.sol | 50 +- contracts/L2MessageReceiver.sol | 87 +- contracts/L2TokenReceiver.sol | 57 +- contracts/MOR.sol | 15 +- contracts/interfaces/IDistribution.sol | 47 + contracts/interfaces/IL2MessageReceiver.sol | 72 +- contracts/interfaces/IL2TokenReceiver.sol | 42 +- contracts/interfaces/IMOR.sol | 8 +- .../LinearDistributionIntervalDecrease.sol | 4 +- contracts/mock/GatewayRouterMock.sol | 4 + contracts/mock/L1SenderV2.sol | 12 + contracts/mock/L2MessageReceiverV2.sol | 12 + contracts/mock/L2TokenReceiverV2.sol | 12 + contracts/mock/SwapRouterMock.sol | 2 +- contracts/mock/tokens/StETHMock.sol | 95 +- contracts/mock/tokens/WStETHMock.sol | 2 +- deploy/1_bridge.migration.ts | 45 +- deploy/2_token.migration.ts | 34 +- deploy/3_init_bridge.migration.ts | 20 +- deploy/data/config.json | 22 +- deploy/data/config_goerli.json | 6 +- deploy/data/config_localhost.json | 40 + deploy/data/config_sepolia.json | 12 +- deploy/deploy-all.sh | 9 + deploy/helpers/config-parser.ts | 16 +- package-lock.json | 908 ++++++++++++++++-- package.json | 31 +- scripts/retryPayload.ts | 41 + scripts/tryAddLiquidity.ts | 2 +- scripts/tryTokensSwap.ts | 10 - scripts/utils/constants.ts | 2 + test/Distribution.test.ts | 171 ++-- test/L1Sender.test.ts | 255 ++++- test/L2MessageReceiver.test.ts | 171 +++- test/L2TokenReceiver.test.ts | 112 ++- test/MOR.test.ts | 4 +- test/fork/L1Sender.fork.test.ts | 27 +- test/fork/L2TokenReceiver.fork.test.ts | 37 +- test/helpers/distribution-helper.ts | 1 + test/helpers/reverter.ts | 1 + 43 files changed, 2092 insertions(+), 474 deletions(-) create mode 100644 contracts/mock/L1SenderV2.sol create mode 100644 contracts/mock/L2MessageReceiverV2.sol create mode 100644 contracts/mock/L2TokenReceiverV2.sol create mode 100644 deploy/data/config_localhost.json create mode 100755 deploy/deploy-all.sh create mode 100644 scripts/retryPayload.ts diff --git a/.eslintrc.json b/.eslintrc.json index 446ede9..402a5fb 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -2,7 +2,8 @@ "parser": "@typescript-eslint/parser", "parserOptions": { "ecmaVersion": 12, - "sourceType": "module" + "sourceType": "module", + "project": "./tsconfig.json" }, "plugins": ["@typescript-eslint"], "extends": [ @@ -11,7 +12,8 @@ "plugin:prettier/recommended" ], "rules": { - "@typescript-eslint/no-unused-vars": "error" + "@typescript-eslint/no-unused-vars": "error", + "@typescript-eslint/no-floating-promises": "error" }, "env": { "browser": true, diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index c65307c..ca3d009 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -17,5 +17,7 @@ jobs: uses: actions/checkout@v3 - name: Setup uses: ./.github/actions/setup + - name: Run lint + run: npm run lint - name: Run tests - run: npm run test-without-fork + run: npm run test diff --git a/contracts/Distribution.sol b/contracts/Distribution.sol index 78cca71..0b5708c 100644 --- a/contracts/Distribution.sol +++ b/contracts/Distribution.sol @@ -1,16 +1,16 @@ // 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 {SafeERC20, IERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; + import {PRECISION} from "@solarity/solidity-lib/utils/Globals.sol"; import {LinearDistributionIntervalDecrease} from "./libs/LinearDistributionIntervalDecrease.sol"; -import {IDistribution} from "./interfaces/IDistribution.sol"; -import {IMOR} from "./interfaces/IMOR.sol"; import {L1Sender} from "./L1Sender.sol"; +import {IDistribution} from "./interfaces/IDistribution.sol"; contract Distribution is IDistribution, OwnableUpgradeable, UUPSUpgradeable { using SafeERC20 for IERC20; @@ -54,7 +54,7 @@ contract Distribution is IDistribution, OwnableUpgradeable, UUPSUpgradeable { __Ownable_init(); __UUPSUpgradeable_init(); - for (uint256 i = 0; i < poolsInfo_.length; i++) { + for (uint256 i; i < poolsInfo_.length; ++i) { createPool(poolsInfo_[i]); } @@ -70,6 +70,8 @@ contract Distribution is IDistribution, OwnableUpgradeable, UUPSUpgradeable { _validatePool(pool_); pools.push(pool_); + + emit PoolCreated(pools.length - 1, pool_); } function editPool(uint256 poolId_, Pool calldata pool_) external onlyOwner poolExists(poolId_) { @@ -84,6 +86,8 @@ contract Distribution is IDistribution, OwnableUpgradeable, UUPSUpgradeable { poolData.lastUpdate = uint128(block.timestamp); pools[poolId_] = pool_; + + emit PoolEdited(poolId_, pool_); } function getPeriodReward(uint256 poolId_, uint128 startTime_, uint128 endTime_) public view returns (uint256) { @@ -104,7 +108,7 @@ contract Distribution is IDistribution, OwnableUpgradeable, UUPSUpgradeable { ); } - function _validatePool(Pool calldata pool_) internal pure { + function _validatePool(Pool calldata pool_) private pure { if (pool_.rewardDecrease > 0) { require(pool_.decreaseInterval > 0, "DS: invalid reward decrease"); } @@ -123,7 +127,7 @@ contract Distribution is IDistribution, OwnableUpgradeable, UUPSUpgradeable { uint256 currentPoolRate_ = _getCurrentPoolRate(poolId_); - for (uint256 i = 0; i < users_.length; i++) { + for (uint256 i; i < users_.length; ++i) { address user_ = users_[i]; uint256 amount_ = amounts_[i]; @@ -165,6 +169,8 @@ contract Distribution is IDistribution, OwnableUpgradeable, UUPSUpgradeable { // Transfer rewards L1Sender(l1Sender).sendMintMessage{value: msg.value}(user_, pendingRewards_, _msgSender()); + + emit UserClaimed(poolId_, user_, pendingRewards_); } function withdraw(uint256 poolId_, uint256 amount_) external poolExists(poolId_) poolPublic(poolId_) { @@ -182,7 +188,7 @@ contract Distribution is IDistribution, OwnableUpgradeable, UUPSUpgradeable { return _getCurrentUserReward(currentPoolRate_, userData); } - function _stake(address user_, uint256 poolId_, uint256 amount_, uint256 currentPoolRate_) internal { + function _stake(address user_, uint256 poolId_, uint256 amount_, uint256 currentPoolRate_) private { require(amount_ > 0, "DS: nothing to stake"); Pool storage pool = pools[poolId_]; @@ -210,11 +216,14 @@ contract Distribution is IDistribution, OwnableUpgradeable, UUPSUpgradeable { poolData.totalDeposited += amount_; // Update user data + userData.lastStake = uint128(block.timestamp); userData.rate = currentPoolRate_; userData.deposited += amount_; + + emit UserStaked(poolId_, user_, amount_); } - function _withdraw(address user_, uint256 poolId_, uint256 amount_, uint256 currentPoolRate_) internal { + function _withdraw(address user_, uint256 poolId_, uint256 amount_, uint256 currentPoolRate_) private { Pool storage pool = pools[poolId_]; PoolData storage poolData = poolsData[poolId_]; UserData storage userData = usersData[user_][poolId_]; @@ -229,7 +238,9 @@ contract Distribution is IDistribution, OwnableUpgradeable, UUPSUpgradeable { uint256 newDeposited_; if (pool.isPublic) { require( - block.timestamp < pool.payoutStart || block.timestamp > pool.payoutStart + pool.withdrawLockPeriod, + block.timestamp < pool.payoutStart || + (block.timestamp > pool.payoutStart + pool.withdrawLockPeriod && + block.timestamp > userData.lastStake + pool.withdrawLockPeriodAfterStake), "DS: pool withdraw is locked" ); @@ -240,10 +251,8 @@ contract Distribution is IDistribution, OwnableUpgradeable, UUPSUpgradeable { newDeposited_ = deposited_ - amount_; - require( - amount_ > 0 && (newDeposited_ >= pool.minimalStake || newDeposited_ == 0), - "DS: invalid withdraw amount" - ); + require(amount_ > 0, "DS: nothing to withdraw"); + require(newDeposited_ >= pool.minimalStake || newDeposited_ == 0, "DS: invalid withdraw amount"); } else { newDeposited_ = deposited_ - amount_; } @@ -265,18 +274,17 @@ contract Distribution is IDistribution, OwnableUpgradeable, UUPSUpgradeable { IERC20(depositToken).safeTransfer(user_, amount_); } + + emit UserWithdrawn(poolId_, user_, amount_); } - function _getCurrentUserReward( - uint256 currentPoolRate_, - UserData memory userData_ - ) internal pure returns (uint256) { + function _getCurrentUserReward(uint256 currentPoolRate_, UserData memory userData_) private pure returns (uint256) { uint256 newRewards_ = ((currentPoolRate_ - userData_.rate) * userData_.deposited) / PRECISION; return userData_.pendingRewards + newRewards_; } - function _getCurrentPoolRate(uint256 poolId_) internal view returns (uint256) { + function _getCurrentPoolRate(uint256 poolId_) private view returns (uint256) { PoolData storage poolData = poolsData[poolId_]; if (poolData.totalDeposited == 0) { @@ -288,7 +296,7 @@ contract Distribution is IDistribution, OwnableUpgradeable, UUPSUpgradeable { return poolData.rate + (rewards_ * PRECISION) / poolData.totalDeposited; } - function _poolExists(uint256 poolId_) internal view returns (bool) { + function _poolExists(uint256 poolId_) private view returns (bool) { return poolId_ < pools.length; } @@ -315,7 +323,15 @@ contract Distribution is IDistribution, OwnableUpgradeable, UUPSUpgradeable { IERC20(depositToken).safeTransfer(l1Sender, overplus_); - return L1Sender(l1Sender).sendDepositToken(gasLimit_, maxFeePerGas_, maxSubmissionCost_); + bytes memory bridgeMessageId_ = L1Sender(l1Sender).sendDepositToken{value: msg.value}( + gasLimit_, + maxFeePerGas_, + maxSubmissionCost_ + ); + + emit OverplusBridged(overplus_, bridgeMessageId_); + + return bridgeMessageId_; } /**********************************************************************************************/ diff --git a/contracts/L1Sender.sol b/contracts/L1Sender.sol index e779081..deea6cd 100644 --- a/contracts/L1Sender.sol +++ b/contracts/L1Sender.sol @@ -5,26 +5,43 @@ import {ILayerZeroEndpoint} from "@layerzerolabs/lz-evm-sdk-v1-0.7/contracts/int import {IGatewayRouter} from "@arbitrum/token-bridge-contracts/contracts/tokenbridge/libraries/gateway/IGatewayRouter.sol"; -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; -import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; +import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; -import {IWStETH} from "./interfaces/tokens/IWStETH.sol"; -import {IStETH} from "./interfaces/tokens/IStETH.sol"; -import {IMOR} from "./interfaces/IMOR.sol"; import {IL1Sender} from "./interfaces/IL1Sender.sol"; +import {IWStETH} from "./interfaces/tokens/IWStETH.sol"; -contract L1Sender is IL1Sender, ERC165, Ownable { +contract L1Sender is IL1Sender, ERC165, OwnableUpgradeable, UUPSUpgradeable { address public unwrappedDepositToken; + address public distribution; DepositTokenConfig public depositTokenConfig; RewardTokenConfig public rewardTokenConfig; - function setRewardTokenConfig(RewardTokenConfig calldata newConfig_) external onlyOwner { + function L1Sender__init( + address distribution_, + RewardTokenConfig calldata rewardTokenConfig_, + DepositTokenConfig calldata depositTokenConfig_ + ) external initializer { + __Ownable_init(); + __UUPSUpgradeable_init(); + + setDistribution(distribution_); + setRewardTokenConfig(rewardTokenConfig_); + setDepositTokenConfig(depositTokenConfig_); + } + + function setDistribution(address distribution_) public onlyOwner { + distribution = distribution_; + } + + function setRewardTokenConfig(RewardTokenConfig calldata newConfig_) public onlyOwner { rewardTokenConfig = newConfig_; } - function setDepositTokenConfig(DepositTokenConfig calldata newConfig_) external onlyOwner { + function setDepositTokenConfig(DepositTokenConfig calldata newConfig_) public onlyOwner { require(newConfig_.receiver != address(0), "L1S: invalid receiver"); DepositTokenConfig storage oldConfig = depositTokenConfig; @@ -59,15 +76,14 @@ contract L1Sender is IL1Sender, ERC165, Ownable { address oldToken_, address newToken_ ) private { - bool isTokenChanged_ = oldToken_ != newToken_; - bool isGatewayChanged_ = oldGateway_ != newGateway_; + bool isAllowedChanged_ = (oldToken_ != newToken_) || (oldGateway_ != newGateway_); - if (oldGateway_ != address(0) && (isTokenChanged_ || isGatewayChanged_)) { - IERC20(oldToken_).approve(oldGateway_, 0); + if (oldGateway_ != address(0) && isAllowedChanged_) { + IERC20(oldToken_).approve(IGatewayRouter(oldGateway_).getGateway(oldToken_), 0); } - if (isTokenChanged_ || isGatewayChanged_) { - IERC20(newToken_).approve(newGateway_, type(uint256).max); + if (isAllowedChanged_) { + IERC20(newToken_).approve(IGatewayRouter(newGateway_).getGateway(newToken_), type(uint256).max); } } @@ -96,7 +112,9 @@ contract L1Sender is IL1Sender, ERC165, Ownable { ); } - function sendMintMessage(address user_, uint256 amount_, address refundTo_) external payable onlyOwner { + function sendMintMessage(address user_, uint256 amount_, address refundTo_) external payable { + require(_msgSender() == distribution, "L1S: invalid sender"); + RewardTokenConfig storage config = rewardTokenConfig; bytes memory receiverAndSenderAddresses_ = abi.encodePacked(config.receiver, address(this)); @@ -111,4 +129,6 @@ contract L1Sender is IL1Sender, ERC165, Ownable { bytes("") // adapterParams (see "Advanced Features") ); } + + function _authorizeUpgrade(address) internal view override onlyOwner {} } diff --git a/contracts/L2MessageReceiver.sol b/contracts/L2MessageReceiver.sol index 751c855..9cbafeb 100644 --- a/contracts/L2MessageReceiver.sol +++ b/contracts/L2MessageReceiver.sol @@ -1,21 +1,27 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import {ILayerZeroReceiver} from "@layerzerolabs/lz-evm-sdk-v1-0.7/contracts/interfaces/ILayerZeroReceiver.sol"; import {IMOR} from "./interfaces/IMOR.sol"; -import {IL1Sender} from "./interfaces/IL1Sender.sol"; import {IL2MessageReceiver} from "./interfaces/IL2MessageReceiver.sol"; -contract L2MessageReceiver is IL2MessageReceiver, ILayerZeroReceiver, Ownable { - uint64 public nonce; +contract L2MessageReceiver is ILayerZeroReceiver, IL2MessageReceiver, OwnableUpgradeable, UUPSUpgradeable { address public rewardToken; Config public config; + mapping(uint16 => mapping(uint64 => bool)) public isNonceUsed; + mapping(uint16 => mapping(bytes => mapping(uint64 => bytes32))) public failedMessages; + + function L2MessageReceiver__init() external initializer { + __Ownable_init(); + __UUPSUpgradeable_init(); + } + function setParams(address rewardToken_, Config calldata config_) external onlyOwner { rewardToken = rewardToken_; config = config_; @@ -23,24 +29,85 @@ contract L2MessageReceiver is IL2MessageReceiver, ILayerZeroReceiver, Ownable { function lzReceive( uint16 senderChainId_, - bytes memory receiverAndSenderAddresses_, + bytes memory senderAndReceiverAddresses_, uint64 nonce_, bytes memory payload_ ) external { - require(nonce_ > nonce, "L2MR: invalid nonce"); require(_msgSender() == config.gateway, "L2MR: invalid gateway"); + + _blockingLzReceive(senderChainId_, senderAndReceiverAddresses_, nonce_, payload_); + } + + function nonblockingLzReceive( + uint16 senderChainId_, + bytes memory senderAndReceiverAddresses_, + uint64 nonce_, + bytes memory payload_ + ) public { + require(_msgSender() == address(this), "L2MR: invalid caller"); + + _nonblockingLzReceive(senderChainId_, senderAndReceiverAddresses_, nonce_, payload_); + } + + function retryMessage( + uint16 senderChainId_, + bytes memory senderAndReceiverAddresses_, + uint64 nonce_, + bytes memory payload_ + ) external { + bytes32 payloadHash_ = failedMessages[senderChainId_][senderAndReceiverAddresses_][nonce_]; + require(payloadHash_ != bytes32(0), "L2MR: no stored message"); + require(keccak256(payload_) == payloadHash_, "L2MR: invalid payload"); + + _nonblockingLzReceive(senderChainId_, senderAndReceiverAddresses_, nonce_, payload_); + + delete failedMessages[senderChainId_][senderAndReceiverAddresses_][nonce_]; + + emit RetryMessageSuccess(senderChainId_, senderAndReceiverAddresses_, nonce_, payload_); + } + + function _blockingLzReceive( + uint16 senderChainId_, + bytes memory senderAndReceiverAddresses_, + uint64 nonce_, + bytes memory payload_ + ) private { + try + IL2MessageReceiver(address(this)).nonblockingLzReceive( + senderChainId_, + senderAndReceiverAddresses_, + nonce_, + payload_ + ) + { + emit MessageSuccess(senderChainId_, senderAndReceiverAddresses_, nonce_, payload_); + } catch (bytes memory reason_) { + failedMessages[senderChainId_][senderAndReceiverAddresses_][nonce_] = keccak256(payload_); + + emit MessageFailed(senderChainId_, senderAndReceiverAddresses_, nonce_, payload_, reason_); + } + } + + function _nonblockingLzReceive( + uint16 senderChainId_, + bytes memory senderAndReceiverAddresses_, + uint64 nonce_, + bytes memory payload_ + ) private { + require(!isNonceUsed[senderChainId_][nonce_], "L2MR: invalid nonce"); require(senderChainId_ == config.senderChainId, "L2MR: invalid sender chain ID"); address sender_; assembly { - sender_ := mload(add(receiverAndSenderAddresses_, 20)) + sender_ := mload(add(senderAndReceiverAddresses_, 20)) } require(sender_ == config.sender, "L2MR: invalid sender address"); (address user_, uint256 amount_) = abi.decode(payload_, (address, uint256)); - nonce = nonce_; _mintRewardTokens(user_, amount_); + + isNonceUsed[senderChainId_][nonce_] = true; } function _mintRewardTokens(address user_, uint256 amount_) private { @@ -56,4 +123,6 @@ contract L2MessageReceiver is IL2MessageReceiver, ILayerZeroReceiver, Ownable { IMOR(rewardToken).mint(user_, amount_); } + + function _authorizeUpgrade(address) internal view override onlyOwner {} } diff --git a/contracts/L2TokenReceiver.sol b/contracts/L2TokenReceiver.sol index 234eb8b..3ad6bff 100644 --- a/contracts/L2TokenReceiver.sol +++ b/contracts/L2TokenReceiver.sol @@ -1,33 +1,40 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; -import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; -import {TransferHelper} from "@uniswap/v3-periphery/contracts/libraries/TransferHelper.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 {INonfungiblePositionManager} from "./interfaces/uniswap-v3/INonfungiblePositionManager.sol"; import {IL2TokenReceiver, IERC165} from "./interfaces/IL2TokenReceiver.sol"; -import {IWStETH} from "./interfaces/tokens/IWStETH.sol"; +import {INonfungiblePositionManager} from "./interfaces/uniswap-v3/INonfungiblePositionManager.sol"; -contract L2TokenReceiver is IL2TokenReceiver, ERC165, Ownable { - address public immutable router; - address public immutable nonfungiblePositionManager; +contract L2TokenReceiver is IL2TokenReceiver, OwnableUpgradeable, UUPSUpgradeable { + address public router; + address public nonfungiblePositionManager; SwapParams public params; - constructor(address router_, address nonfungiblePositionManager_, SwapParams memory params_) { + function L2TokenReceiver__init( + address router_, + address nonfungiblePositionManager_, + SwapParams memory params_ + ) external initializer { + __Ownable_init(); + __UUPSUpgradeable_init(); + router = router_; nonfungiblePositionManager = nonfungiblePositionManager_; _editParams(params_); } - function supportsInterface(bytes4 interfaceId_) public view override(ERC165, IERC165) returns (bool) { - return interfaceId_ == type(IL2TokenReceiver).interfaceId || super.supportsInterface(interfaceId_); + function supportsInterface(bytes4 interfaceId_) external pure returns (bool) { + return interfaceId_ == type(IL2TokenReceiver).interfaceId || interfaceId_ == type(IERC165).interfaceId; } - function editParams(SwapParams memory newParams_) public onlyOwner { + function editParams(SwapParams memory newParams_) external onlyOwner { if (params.tokenIn != newParams_.tokenIn) { TransferHelper.safeApprove(params.tokenIn, router, 0); TransferHelper.safeApprove(params.tokenIn, nonfungiblePositionManager, 0); @@ -54,16 +61,24 @@ contract L2TokenReceiver is IL2TokenReceiver, ERC165, Ownable { sqrtPriceLimitX96: params_.sqrtPriceLimitX96 }); - return ISwapRouter(router).exactInputSingle(swapParams_); + 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 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_ @@ -71,9 +86,13 @@ contract L2TokenReceiver is IL2TokenReceiver, ERC165, Ownable { 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 @@ -81,17 +100,19 @@ contract L2TokenReceiver is IL2TokenReceiver, ERC165, Ownable { tokenId: tokenId_, amount0Desired: amountAdd0_, amount1Desired: amountAdd1_, - amount0Min: 0, - amount1Min: 0, + amount0Min: amountMin0_, + amount1Min: amountMin1_, deadline: block.timestamp }); (liquidity_, amount0_, amount1_) = INonfungiblePositionManager(nonfungiblePositionManager).increaseLiquidity( params_ ); + + emit LiquidityIncreased(tokenId_, amount0_, amount1_, liquidity_, amountMin0_, amountMin1_); } - function _editParams(SwapParams memory newParams_) internal { + function _editParams(SwapParams memory newParams_) private { require(newParams_.tokenIn != address(0), "L2TR: invalid tokenIn"); require(newParams_.tokenOut != address(0), "L2TR: invalid tokenOut"); @@ -102,4 +123,6 @@ contract L2TokenReceiver is IL2TokenReceiver, ERC165, Ownable { params = newParams_; } + + function _authorizeUpgrade(address) internal view override onlyOwner {} } diff --git a/contracts/MOR.sol b/contracts/MOR.sol index 9cf74d2..9110c9f 100644 --- a/contracts/MOR.sol +++ b/contracts/MOR.sol @@ -1,21 +1,20 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -import {ERC20, ERC20Capped} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Capped.sol"; -import {ERC20Burnable} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol"; -import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {ERC20Burnable} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol"; +import {ERC20, ERC20Capped} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Capped.sol"; -import {IMOR, IERC165, IERC20} from "./interfaces/IMOR.sol"; +import {IMOR, IERC20, IERC165} from "./interfaces/IMOR.sol"; -contract MOR is IMOR, ERC165, ERC20Capped, ERC20Burnable, Ownable { +contract MOR is IMOR, ERC20Capped, ERC20Burnable, Ownable { constructor(uint256 cap_) ERC20("MOR", "MOR") ERC20Capped(cap_) {} - function supportsInterface(bytes4 interfaceId_) public view override(ERC165, IERC165) returns (bool) { + function supportsInterface(bytes4 interfaceId_) external pure returns (bool) { return interfaceId_ == type(IMOR).interfaceId || interfaceId_ == type(IERC20).interfaceId || - super.supportsInterface(interfaceId_); + interfaceId_ == type(IERC165).interfaceId; } function cap() public view override(IMOR, ERC20Capped) returns (uint256) { @@ -26,7 +25,7 @@ contract MOR is IMOR, ERC165, ERC20Capped, ERC20Burnable, Ownable { _mint(account_, amount_); } - function burn(uint256 amount_) public override(IMOR, ERC20Burnable) { + function burn(uint256 amount_) public override { ERC20Burnable.burn(amount_); } diff --git a/contracts/interfaces/IDistribution.sol b/contracts/interfaces/IDistribution.sol index 9920536..cb16a23 100644 --- a/contracts/interfaces/IDistribution.sol +++ b/contracts/interfaces/IDistribution.sol @@ -11,6 +11,7 @@ interface IDistribution { * @param payoutStart The timestamp when the pool starts to pay out rewards. * @param decreaseInterval The interval in seconds between reward decreases. * @param withdrawLockPeriod The period in seconds when the user can't withdraw his stake. + * @param withdrawLockPeriodAfterStake The period in seconds when the user can't withdraw his stake after staking. * @param claimLockPeriod The period in seconds when the user can't claim his rewards. * @param initialReward The initial reward per interval. * @param rewardDecrease The reward decrease per interval. @@ -22,6 +23,7 @@ interface IDistribution { uint128 decreaseInterval; uint128 withdrawLockPeriod; uint128 claimLockPeriod; + uint128 withdrawLockPeriodAfterStake; uint256 initialReward; uint256 rewardDecrease; uint256 minimalStake; @@ -42,16 +44,61 @@ interface IDistribution { /** * The structure that stores the user's rate data of pool. + * @param lastStake The timestamp when the user last staked tokens. * @param deposited The amount of tokens deposited in the pool. * @param rate The current reward rate. * @param pendingRewards The amount of pending rewards. */ struct UserData { + uint128 lastStake; uint256 deposited; uint256 rate; uint256 pendingRewards; } + /** + * The event that is emitted when the pool is created. + * @param poolId The pool's id. + * @param pool The pool's data. + */ + event PoolCreated(uint256 indexed poolId, Pool pool); + + /** + * The event that is emitted when the pool is edited. + * @param poolId The pool's id. + * @param pool The pool's data. + */ + event PoolEdited(uint256 indexed poolId, Pool pool); + + /** + * The event that is emitted when the user stakes tokens in the pool. + * @param poolId The pool's id. + * @param user The user's address. + * @param amount The amount of tokens. + */ + event UserStaked(uint256 indexed poolId, address indexed user, uint256 amount); + + /** + * The event that is emitted when the user claims rewards from the pool. + * @param poolId The pool's id. + * @param user The user's address. + * @param amount The amount of tokens. + */ + event UserClaimed(uint256 indexed poolId, address indexed user, uint256 amount); + + /** + * The event that is emitted when the user withdraws tokens from the pool. + * @param poolId The pool's id. + * @param user The user's address. + * @param amount The amount of tokens. + */ + event UserWithdrawn(uint256 indexed poolId, address indexed user, uint256 amount); + + /** + * The event that is emitted when the overplus of the deposit tokens is bridged. + */ + event OverplusBridged(uint256 amount, bytes uniqueId); + /** * The function to initialize the contract. * @param depositToken_ The address of deposit token. diff --git a/contracts/interfaces/IL2MessageReceiver.sol b/contracts/interfaces/IL2MessageReceiver.sol index e4916da..3dd3668 100644 --- a/contracts/interfaces/IL2MessageReceiver.sol +++ b/contracts/interfaces/IL2MessageReceiver.sol @@ -1,7 +1,43 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -interface IL2MessageReceiver { +import {ILayerZeroReceiver} from "@layerzerolabs/lz-evm-sdk-v1-0.7/contracts/interfaces/ILayerZeroReceiver.sol"; + +interface IL2MessageReceiver is ILayerZeroReceiver { + /** + * The event that is emitted when the message is received. + * @param senderChainId The source endpoint identifier. + * @param senderAndReceiverAddresses The source sending contract address from the source chain. + * @param nonce The ordered message nonce. + * @param payload The signed payload is the UA bytes has encoded to be sent. + */ + event MessageSuccess(uint16 senderChainId, bytes senderAndReceiverAddresses, uint64 nonce, bytes payload); + + /** + * The event that is emitted when the message is failed. + * @param senderChainId The source endpoint identifier. + * @param senderAndReceiverAddresses The source sending contract address from the source chain. + * @param nonce The ordered message nonce. + * @param payload The signed payload is the UA bytes has encoded to be sent. + * @param reason The reason of failure. + */ + event MessageFailed( + uint16 senderChainId, + bytes senderAndReceiverAddresses, + uint64 nonce, + bytes payload, + bytes reason + ); + + /** + * The event that is emitted when the message is retried. + * @param senderChainId The source endpoint identifier. + * @param senderAndReceiverAddresses The source sending contract address from the source chain. + * @param nonce The ordered message nonce. + * @param payload The signed payload is the UA bytes has encoded to be sent. + */ + event RetryMessageSuccess(uint16 senderChainId, bytes senderAndReceiverAddresses, uint64 nonce, bytes payload); + /** * The structure that stores the config data. * @param gateway The address of token's gateway. @@ -14,12 +50,6 @@ interface IL2MessageReceiver { uint16 senderChainId; } - /** - * The function to get the nonce of obtained messages. - * @return The nonce. - */ - function nonce() external view returns (uint64); - /** * The function to get the reward token's address. * @return The address of reward token. @@ -32,4 +62,32 @@ interface IL2MessageReceiver { * @param config_ The config data. */ function setParams(address rewardToken_, Config calldata config_) external; + + /** + * LayerZero endpoint call this function to check a transaction capabilities. + * @param senderChainId_ The source endpoint identifier. + * @param senderAndReceiverAddresses_ The source sending contract address from the source chain. + * @param nonce_ The ordered message nonce. + * @param payload_ The signed payload is the UA bytes has encoded to be sent. + */ + function nonblockingLzReceive( + uint16 senderChainId_, + bytes memory senderAndReceiverAddresses_, + uint64 nonce_, + bytes memory payload_ + ) external; + + /** + * Retry to execute the blocked message. + * @param senderChainId_ The source endpoint identifier. + * @param senderAndReceiverAddresses_ The source sending contract address from the source chain. + * @param nonce_ The ordered message nonce. + * @param payload_ The signed payload is the UA bytes has encoded to be sent. + */ + function retryMessage( + uint16 senderChainId_, + bytes memory senderAndReceiverAddresses_, + uint64 nonce_, + bytes memory payload_ + ) external; } diff --git a/contracts/interfaces/IL2TokenReceiver.sol b/contracts/interfaces/IL2TokenReceiver.sol index 8878ab0..341c0f9 100644 --- a/contracts/interfaces/IL2TokenReceiver.sol +++ b/contracts/interfaces/IL2TokenReceiver.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -import {IERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; +import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; /** * This is Swap contract that swaps tokens using Uniswap V3. @@ -21,6 +21,40 @@ interface IL2TokenReceiver is IERC165 { 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 function to edit the swap params. * @param params_ The new swap params. @@ -40,6 +74,8 @@ interface IL2TokenReceiver is IERC165 { * @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. @@ -47,7 +83,9 @@ interface IL2TokenReceiver is IERC165 { function increaseLiquidityCurrentRange( uint256 tokenId, uint256 amountAdd0_, - uint256 amountAdd1_ + uint256 amountAdd1_, + uint256 depositTokenAmountMin_, + uint256 rewardTokenAmountMin_ ) external returns (uint128 liquidity_, uint256 amount0_, uint256 amount1_); /** diff --git a/contracts/interfaces/IMOR.sol b/contracts/interfaces/IMOR.sol index df58b22..49f138e 100644 --- a/contracts/interfaces/IMOR.sol +++ b/contracts/interfaces/IMOR.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.20; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import {IERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.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. @@ -20,10 +20,4 @@ interface IMOR is IERC20, IERC165 { * @param amount_ The amount of tokens to mint. */ function mint(address account_, uint256 amount_) external; - - /** - * The function to burn tokens. - * @param amount_ The amount of tokens to burn. - */ - function burn(uint256 amount_) external; } diff --git a/contracts/libs/LinearDistributionIntervalDecrease.sol b/contracts/libs/LinearDistributionIntervalDecrease.sol index 1006c6a..9e559d2 100644 --- a/contracts/libs/LinearDistributionIntervalDecrease.sol +++ b/contracts/libs/LinearDistributionIntervalDecrease.sol @@ -23,7 +23,7 @@ library LinearDistributionIntervalDecrease { uint128 interval_, uint128 startTime_, uint128 endTime_ - ) public pure returns (uint256) { + ) external pure returns (uint256) { if (interval_ == 0) { return 0; } @@ -152,7 +152,7 @@ library LinearDistributionIntervalDecrease { return 0; } - return initialReward_ * ip_ - (decreaseAmount_ * ((1 + (ip_ - 1)) * (ip_ - 1))) / 2; + return initialReward_ * ip_ - (decreaseAmount_ * (ip_ * (ip_ - 1))) / 2; } function _divideCeil(uint256 a_, uint256 b_) private pure returns (uint256) { diff --git a/contracts/mock/GatewayRouterMock.sol b/contracts/mock/GatewayRouterMock.sol index 534591f..185b417 100644 --- a/contracts/mock/GatewayRouterMock.sol +++ b/contracts/mock/GatewayRouterMock.sol @@ -16,4 +16,8 @@ contract GatewayRouterMock { return abi.encode(_token, _to, _amount, _maxGas, _gasPriceBid, _data); } + + function getGateway(address) external view returns (address) { + return address(this); + } } diff --git a/contracts/mock/L1SenderV2.sol b/contracts/mock/L1SenderV2.sol new file mode 100644 index 0000000..975d090 --- /dev/null +++ b/contracts/mock/L1SenderV2.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 L1SenderV2 is UUPSUpgradeable { + function version() external pure returns (uint256) { + return 2; + } + + function _authorizeUpgrade(address) internal view override {} +} diff --git a/contracts/mock/L2MessageReceiverV2.sol b/contracts/mock/L2MessageReceiverV2.sol new file mode 100644 index 0000000..9170fc9 --- /dev/null +++ b/contracts/mock/L2MessageReceiverV2.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 L2MessageReceiverV2 is UUPSUpgradeable { + function version() external pure returns (uint256) { + return 2; + } + + function _authorizeUpgrade(address) internal view override {} +} diff --git a/contracts/mock/L2TokenReceiverV2.sol b/contracts/mock/L2TokenReceiverV2.sol new file mode 100644 index 0000000..3695d42 --- /dev/null +++ b/contracts/mock/L2TokenReceiverV2.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/SwapRouterMock.sol b/contracts/mock/SwapRouterMock.sol index e474461..b5d11fd 100644 --- a/contracts/mock/SwapRouterMock.sol +++ b/contracts/mock/SwapRouterMock.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.20; import {ISwapRouter} from "@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol"; -import {SafeERC20, IERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; contract SwapRouterMock { function exactInputSingle(ISwapRouter.ExactInputSingleParams calldata params_) external returns (uint256) { diff --git a/contracts/mock/tokens/StETHMock.sol b/contracts/mock/tokens/StETHMock.sol index 8a2b992..cc4ae37 100644 --- a/contracts/mock/tokens/StETHMock.sol +++ b/contracts/mock/tokens/StETHMock.sol @@ -1,31 +1,96 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import {PRECISION} from "@solarity/solidity-lib/utils/Globals.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; -import {IStETH} from "../../interfaces/tokens/IStETH.sol"; +contract StETHMock is ERC20, Ownable { + uint256 public totalShares; + uint256 public totalPooledEther; -contract StETHMock is ERC20 { - uint256 public totalPooledEther = PRECISION; + mapping(address => uint256) private shares; - constructor() ERC20("Staked Ether", "stETH") {} + constructor() ERC20("Staked Ether Mock", "stETHMock") { + _mintShares(address(this), 10 ** decimals()); - function mint(address account_, uint256 amount_) external { - _mint(account_, amount_); + totalPooledEther = 10 ** decimals(); } - function _transfer(address sender_, address recipient_, uint256 amount_) internal override { - amount_ = (amount_ * PRECISION) / totalPooledEther; - super._transfer(sender_, recipient_, amount_); + function mint(address _account, uint256 _amount) external { + require(_amount <= 1000 * (10 ** decimals()), "StETHMock: amount is too big"); + + uint256 sharesAmount = getSharesByPooledEth(_amount); + + _mintShares(_account, sharesAmount); + + totalPooledEther += _amount; + } + + function setTotalPooledEther(uint256 _totalPooledEther) external onlyOwner { + totalPooledEther = _totalPooledEther; + } + + function totalSupply() public view override returns (uint256) { + return totalPooledEther; + } + + function balanceOf(address _account) public view override returns (uint256) { + return getPooledEthByShares(_sharesOf(_account)); + } + + function sharesOf(address _account) external view returns (uint256) { + return _sharesOf(_account); + } + + function getSharesByPooledEth(uint256 _ethAmount) public view returns (uint256) { + return (_ethAmount * totalShares) / totalPooledEther; + } + + function getPooledEthByShares(uint256 _sharesAmount) public view returns (uint256) { + return (_sharesAmount * totalPooledEther) / totalShares; + } + + function transferShares(address _recipient, uint256 _sharesAmount) external returns (uint256) { + _transferShares(msg.sender, _recipient, _sharesAmount); + uint256 tokensAmount = getPooledEthByShares(_sharesAmount); + return tokensAmount; + } + + function transferSharesFrom(address _sender, address _recipient, uint256 _sharesAmount) external returns (uint256) { + uint256 tokensAmount = getPooledEthByShares(_sharesAmount); + _spendAllowance(_sender, msg.sender, tokensAmount); + _transferShares(_sender, _recipient, _sharesAmount); + return tokensAmount; + } + + function _transfer(address _sender, address _recipient, uint256 _amount) internal override { + uint256 _sharesToTransfer = getSharesByPooledEth(_amount); + _transferShares(_sender, _recipient, _sharesToTransfer); } - function setTotalPooledEther(uint256 totalPooledEther_) external { - totalPooledEther = totalPooledEther_; + function _sharesOf(address _account) internal view returns (uint256) { + return shares[_account]; } - function balanceOf(address account_) public view override returns (uint256) { - return (super.balanceOf(account_) * totalPooledEther) / PRECISION; + function _transferShares(address _sender, address _recipient, uint256 _sharesAmount) internal { + require(_sender != address(0), "TRANSFER_FROM_ZERO_ADDR"); + require(_recipient != address(0), "TRANSFER_TO_ZERO_ADDR"); + require(_recipient != address(this), "TRANSFER_TO_STETH_CONTRACT"); + + uint256 currentSenderShares = shares[_sender]; + require(_sharesAmount <= currentSenderShares, "BALANCE_EXCEEDED"); + + shares[_sender] = currentSenderShares - _sharesAmount; + shares[_recipient] += _sharesAmount; + } + + function _mintShares(address _recipient, uint256 _sharesAmount) internal returns (uint256 newTotalShares) { + require(_recipient != address(0), "MINT_TO_ZERO_ADDR"); + + totalShares += _sharesAmount; + + shares[_recipient] += _sharesAmount; + + return totalShares; } } diff --git a/contracts/mock/tokens/WStETHMock.sol b/contracts/mock/tokens/WStETHMock.sol index 628f765..4b7daaa 100644 --- a/contracts/mock/tokens/WStETHMock.sol +++ b/contracts/mock/tokens/WStETHMock.sol @@ -8,7 +8,7 @@ import {IStETH} from "../../interfaces/tokens/IStETH.sol"; contract WStETHMock is ERC20 { IStETH public stETH; - constructor(address stETH_) ERC20("Wraped Staked Ether", "WStETH") { + constructor(address stETH_) ERC20("Wraped Staked Ether Mock", "WStETHMock") { stETH = IStETH(stETH_); } diff --git a/deploy/1_bridge.migration.ts b/deploy/1_bridge.migration.ts index d3c3720..b28d1d2 100644 --- a/deploy/1_bridge.migration.ts +++ b/deploy/1_bridge.migration.ts @@ -1,8 +1,9 @@ -import { DefaultStorage, Deployer, Reporter } from '@solarity/hardhat-migrate'; +import { Deployer, Reporter, UserStorage } from '@solarity/hardhat-migrate'; import { parseConfig } from './helpers/config-parser'; import { + ERC1967Proxy__factory, L2MessageReceiver__factory, L2TokenReceiver__factory, MOR__factory, @@ -14,7 +15,7 @@ import { import { IL2TokenReceiver } from '@/generated-types/ethers/contracts/L2TokenReceiver'; module.exports = async function (deployer: Deployer) { - const config = parseConfig(); + const config = parseConfig(await deployer.getChainId()); let WStETH: string; let swapRouter: string; @@ -40,28 +41,44 @@ module.exports = async function (deployer: Deployer) { } const MOR = await deployer.deploy(MOR__factory, [config.cap]); + if (!UserStorage.has('MOR')) UserStorage.set('MOR', await MOR.getAddress()); const swapParams: IL2TokenReceiver.SwapParamsStruct = { tokenIn: WStETH, - tokenOut: MOR.address, + tokenOut: MOR, fee: config.swapParams.fee, sqrtPriceLimitX96: config.swapParams.sqrtPriceLimitX96, }; - const l2TokenReceiver = await deployer.deploy(L2TokenReceiver__factory, [ - swapRouter, - nonfungiblePositionManager, - swapParams, - ]); - DefaultStorage.set('l2TokenReceiver', l2TokenReceiver.address); - const l2MessageReceiver = await deployer.deploy(L2MessageReceiver__factory); - DefaultStorage.set('l2MessageReceiver', l2MessageReceiver.address); + const l2TokenReceiverImpl = await deployer.deploy(L2TokenReceiver__factory); + const l2TokenReceiverProxy = await deployer.deploy(ERC1967Proxy__factory, [l2TokenReceiverImpl, '0x'], { + name: 'L2TokenReceiver Proxy', + }); + if (!UserStorage.has('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', + }); + if (!UserStorage.has('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', l2TokenReceiver.address], - ['L2MessageReceiver', l2MessageReceiver.address], - ['MOR', MOR.address], + ['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 index 5c5b0c4..8f4f460 100644 --- a/deploy/2_token.migration.ts +++ b/deploy/2_token.migration.ts @@ -1,4 +1,4 @@ -import { DefaultStorage, Deployer, Reporter } from '@solarity/hardhat-migrate'; +import { Deployer, Reporter, UserStorage } from '@solarity/hardhat-migrate'; import { parseConfig } from './helpers/config-parser'; @@ -14,7 +14,7 @@ import { IL1Sender } from '@/generated-types/ethers/contracts/L1Sender'; import { ETHER_ADDR } from '@/scripts/utils/constants'; module.exports = async function (deployer: Deployer) { - const config = parseConfig(); + const config = parseConfig(await deployer.getChainId()); let stETH: string; let wStEth: string; @@ -50,28 +50,29 @@ module.exports = async function (deployer: Deployer) { } const distributionImpl = await deployer.deploy(Distribution__factory); - const ERC1967Proxy = await deployer.deploy(ERC1967Proxy__factory, [distributionImpl, '0x']); - const distribution = Distribution__factory.connect(ERC1967Proxy.address, await deployer.getSigner()); - - const l1Sender = await deployer.deploy(L1Sender__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: DefaultStorage.get('l2MessageReceiver'), - // receiver: '0xc37fF39e5A50543AD01E42C4Cd88c2939dD13002', + receiver: UserStorage.get('L2MessageReceiver Proxy'), receiverChainId: config.chainsConfig.receiverChainId, }; - await l1Sender.setRewardTokenConfig(rewardTokenConfig); - const depositTokenConfig: IL1Sender.DepositTokenConfigStruct = { token: wStEth, gateway: arbitrumBridgeGatewayRouter, - receiver: DefaultStorage.get('l2TokenReceiver'), - // receiver: '0x56c7db3D200c92eAAb8a2c4a9C1DcB8c50D4041F', + receiver: UserStorage.get('L2TokenReceiver Proxy'), }; - await l1Sender.setDepositTokenConfig(depositTokenConfig); - await l1Sender.transferOwnership(await distribution.getAddress()); + const l1SenderImpl = await deployer.deploy(L1Sender__factory); + const l1SenderProxy = await deployer.deploy(ERC1967Proxy__factory, [l1SenderImpl, '0x'], { + name: 'L1Sender Proxy', + }); + if (!UserStorage.has('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 || []); @@ -86,5 +87,8 @@ module.exports = async function (deployer: Deployer) { } } - Reporter.reportContracts(['Distribution', await distribution.getAddress()], ['L1Sender', l1Sender.address]); + 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 index e4c9803..9d4561c 100644 --- a/deploy/3_init_bridge.migration.ts +++ b/deploy/3_init_bridge.migration.ts @@ -1,4 +1,4 @@ -import { Deployer } from '@solarity/hardhat-migrate'; +import { Deployer, UserStorage } from '@solarity/hardhat-migrate'; import { parseConfig } from './helpers/config-parser'; @@ -11,7 +11,7 @@ import { import { IL2MessageReceiver } from '@/generated-types/ethers/contracts/L2MessageReceiver'; module.exports = async function (deployer: Deployer) { - const config = parseConfig(); + const config = parseConfig(await deployer.getChainId()); let lzEndpointL2: string; if (config.lzConfig) { @@ -24,22 +24,20 @@ module.exports = async function (deployer: Deployer) { lzEndpointL2 = await lzEndpointL2Mock.getAddress(); } - const l2MessageReceiver = await deployer.deployed( - L2MessageReceiver__factory, - // '0xc37fF39e5A50543AD01E42C4Cd88c2939dD13002', + const l2MessageReceiver = L2MessageReceiver__factory.connect( + UserStorage.get('L2MessageReceiver Proxy'), + await deployer.getSigner(), ); - const l1SenderAddress = (await deployer.deployed(L1Sender__factory)).address; - // const l1SenderAddress = '0xEec0DF0991458274fF0ede917E9827fFc67a8332'; + const l1Sender = L1Sender__factory.connect(UserStorage.get('L1Sender Proxy'), await deployer.getSigner()); - const morAddress = (await deployer.deployed(MOR__factory)).address; - // const morAddress = '0x26BCDEb3E4e7EDf5657daF543132cAF792728908'; + const mor = MOR__factory.connect(UserStorage.get('MOR'), await deployer.getSigner()); const l2MessageReceiverConfig: IL2MessageReceiver.ConfigStruct = { gateway: lzEndpointL2, - sender: l1SenderAddress, + sender: l1Sender, senderChainId: config.chainsConfig.senderChainId, }; - await l2MessageReceiver.setParams(morAddress, l2MessageReceiverConfig); + await l2MessageReceiver.setParams(mor, l2MessageReceiverConfig); }; diff --git a/deploy/data/config.json b/deploy/data/config.json index 54aca13..7eac752 100644 --- a/deploy/data/config.json +++ b/deploy/data/config.json @@ -1,8 +1,8 @@ { "cap": "1000000000000000000000000", "chainsConfig": { - "senderChainId": 10121, - "receiverChainId": 10143 + "senderChainId": 101, + "receiverChainId": 110 }, "pools": [ { @@ -10,6 +10,7 @@ "decreaseInterval": 86400, "withdrawLockPeriod": 120, "claimLockPeriod": 60, + "withdrawLockPeriodAfterStake": 30, "initialReward": "14400000000000000000000", "rewardDecrease": "2468994701000000000", "minimalStake": "1000000000000000", @@ -20,6 +21,7 @@ "decreaseInterval": 60, "withdrawLockPeriod": 1, "claimLockPeriod": 1, + "withdrawLockPeriodAfterStake": 30, "initialReward": "100000000000000000000", "rewardDecrease": "100000000000000000000", "minimalStake": "1000000000000000", @@ -31,8 +33,24 @@ "amounts": ["1", "1"] } ], + "L1": { + "stEth": "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84", + "wStEth": "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0" + }, + "L2": { + "swapRouter": "0xE592427A0AEce92De3Edee1F18E0157C05861564", + "nonfungiblePositionManager": "0xC36442b4a4522E871399CD717aBDD847Ab11FE88", + "wStEth": "0x5979D7b546E38E414F7E9822514be443A4800529" + }, "swapParams": { "fee": 10000, "sqrtPriceLimitX96": 0 + }, + "arbitrumConfig": { + "arbitrumBridgeGatewayRouter": "0x72Ce9c846789fdB6fC1f34aC4AD25Dd9ef7031ef" + }, + "lzConfig": { + "lzEndpointL1": "0x66A71Dcef29A0fFBDBE3c6a460a3B5BC225Cd675", + "lzEndpointL2": "0x3c2269811836af69497E5F486A85D7316753cf62" } } diff --git a/deploy/data/config_goerli.json b/deploy/data/config_goerli.json index b296b04..9891da6 100644 --- a/deploy/data/config_goerli.json +++ b/deploy/data/config_goerli.json @@ -6,20 +6,22 @@ }, "pools": [ { - "payoutStart": 1703618736, + "payoutStart": 1735925178, "decreaseInterval": 86400, "withdrawLockPeriod": 120, "claimLockPeriod": 60, + "withdrawLockPeriodAfterStake": 30, "initialReward": "14400000000000000000000", "rewardDecrease": "2468994701000000000", "minimalStake": "1000000000000000", "isPublic": true }, { - "payoutStart": 1703618736, + "payoutStart": 1735925178, "decreaseInterval": 60, "withdrawLockPeriod": 1, "claimLockPeriod": 1, + "withdrawLockPeriodAfterStake": 30, "initialReward": "100000000000000000000", "rewardDecrease": "100000000000000000000", "minimalStake": "1000000000000000", diff --git a/deploy/data/config_localhost.json b/deploy/data/config_localhost.json new file mode 100644 index 0000000..4c965c2 --- /dev/null +++ b/deploy/data/config_localhost.json @@ -0,0 +1,40 @@ +{ + "cap": "1000000000000000000000000", + "chainsConfig": { + "senderChainId": 10121, + "receiverChainId": 10143 + }, + "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 + } +} diff --git a/deploy/data/config_sepolia.json b/deploy/data/config_sepolia.json index ba83d7d..e07087a 100644 --- a/deploy/data/config_sepolia.json +++ b/deploy/data/config_sepolia.json @@ -6,23 +6,25 @@ }, "pools": [ { - "payoutStart": 1703672149, + "payoutStart": 1704360948, "decreaseInterval": 86400, "withdrawLockPeriod": 120, "claimLockPeriod": 60, + "withdrawLockPeriodAfterStake": 30, "initialReward": "14400000000000000000000", "rewardDecrease": "2468994701000000000", - "minimalStake": "1000000000000000", + "minimalStake": "10000000000", "isPublic": true }, { - "payoutStart": 1703672149, + "payoutStart": 1704360948, "decreaseInterval": 60, "withdrawLockPeriod": 1, "claimLockPeriod": 1, + "withdrawLockPeriodAfterStake": 30, "initialReward": "100000000000000000000", "rewardDecrease": "100000000000000000000", - "minimalStake": "1000000000000000", + "minimalStake": "10000000000", "isPublic": false, "whitelistedUsers": [ "0x901F2d23823730fb7F2356920e0E273EFdCdFe17", @@ -38,7 +40,7 @@ "L2": { "swapRouter": "0xE592427A0AEce92De3Edee1F18E0157C05861564", "nonfungiblePositionManager": "0xC36442b4a4522E871399CD717aBDD847Ab11FE88", - "wStEth": "0xed2f149221ECf2Cf4e929560B8E42a94555c3986" + "wStEth": "0xfD5d8Dc9918e04673b9F5F332cE63BcC3BE427a2" }, "swapParams": { "fee": 10000, diff --git a/deploy/deploy-all.sh b/deploy/deploy-all.sh new file mode 100755 index 0000000..15372bc --- /dev/null +++ b/deploy/deploy-all.sh @@ -0,0 +1,9 @@ +#!/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 ffe1b01..e7b6636 100644 --- a/deploy/helpers/config-parser.ts +++ b/deploy/helpers/config-parser.ts @@ -38,7 +38,21 @@ type PoolInitInfo = IDistribution.PoolStruct & { amounts: BigNumberish[]; }; -export function parseConfig(configPath: string = 'deploy/data/config_goerli.json'): Config { +export function parseConfig(chainId: bigint): Config { + let configPath: string; + + if (chainId === 31337n) { + configPath = `deploy/data/config_localhost.json`; + } else if (chainId === 1n || chainId === 42161n) { + configPath = `deploy/data/config.json`; + } else if (chainId === 5n || chainId === 421613n) { + configPath = `deploy/data/config_goerli.json`; + } else if (chainId === 11155111n || chainId === 421614n) { + configPath = `deploy/data/config_sepolia.json`; + } else { + throw new Error(`Invalid chainId`); + } + const config: Config = JSON.parse(readFileSync(configPath, 'utf-8')) as Config; if (config.cap == undefined) { diff --git a/package-lock.json b/package-lock.json index d83b532..36ebd1d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,7 +30,7 @@ "@nomiclabs/hardhat-truffle5": "^2.0.7", "@nomiclabs/hardhat-web3": "^2.0.0", "@solarity/hardhat-markup": "^1.0.2", - "@solarity/hardhat-migrate": "2.0.0-alpha.14", + "@solarity/hardhat-migrate": "2.0.1", "@trivago/prettier-plugin-sort-imports": "^4.3.0", "@typechain/ethers-v6": "^0.5.0", "@typechain/hardhat": "^9.0.0", @@ -50,7 +50,9 @@ "husky": "^8.0.2", "mocha": "^10.2.0", "prettier": "^3.1.0", - "prettier-plugin-solidity": "^1.2.0", + "prettier-plugin-solidity": "^1.3.1", + "solhint": "^4.0.0", + "solhint-plugin-prettier": "^0.1.0", "solidity-coverage": "^0.8.5", "ts-node": "^10.9.1", "tsconfig-paths": "^4.2.0", @@ -68,11 +70,10 @@ } }, "node_modules/@adraffy/ens-normalize": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.9.2.tgz", - "integrity": "sha512-0h+FrQDqe2Wn+IIGFkTCd4aAwTJ+7834Ek1COohCyV26AXhwQ7WQaz+4F/nLOeVl/3BtWHOHLPsq46V8YB46Eg==", - "dev": true, - "peer": true + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.0.tgz", + "integrity": "sha512-nA9XHtlAkYfJxY7bce8DcN7eKxWWCWkU+1GR9d+U6MbNpfwQp8TI7vqOsBsMcHoT4mBu2kypKoSKnghEzOOq5Q==", + "dev": true }, "node_modules/@aduh95/viz.js": { "version": "3.7.0", @@ -2992,9 +2993,9 @@ } }, "node_modules/@nomicfoundation/hardhat-ethers": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-ethers/-/hardhat-ethers-3.0.4.tgz", - "integrity": "sha512-k9qbLoY7qn6C6Y1LI0gk2kyHXil2Tauj4kGzQ8pgxYXIGw8lWn8tuuL72E11CrlKaXRUvOgF0EXrv/msPI2SbA==", + "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", @@ -3565,6 +3566,59 @@ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", "dev": true }, + "node_modules/@pnpm/config.env-replace": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz", + "integrity": "sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==", + "dev": true, + "engines": { + "node": ">=12.22.0" + } + }, + "node_modules/@pnpm/network.ca-file": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@pnpm/network.ca-file/-/network.ca-file-1.0.2.tgz", + "integrity": "sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==", + "dev": true, + "dependencies": { + "graceful-fs": "4.2.10" + }, + "engines": { + "node": ">=12.22.0" + } + }, + "node_modules/@pnpm/network.ca-file/node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "node_modules/@pnpm/npm-conf": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@pnpm/npm-conf/-/npm-conf-2.2.2.tgz", + "integrity": "sha512-UA91GwWPhFExt3IizW6bOeY/pQ0BkuNwKjk9iQW9KqxluGCrg4VenZ0/L+2Y0+ZOtme72EVvg6v0zo3AMQRCeA==", + "dev": true, + "dependencies": { + "@pnpm/config.env-replace": "^1.1.0", + "@pnpm/network.ca-file": "^1.0.1", + "config-chain": "^1.1.11" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@prettier/sync": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@prettier/sync/-/sync-0.3.0.tgz", + "integrity": "sha512-3dcmCyAxIcxy036h1I7MQU/uEEBq8oLwf1CE3xeze+MPlgkdlb/+w6rGR/1dhp6Hqi17fRS6nvwnOzkESxEkOw==", + "dev": true, + "funding": { + "url": "https://github.com/prettier/prettier-synchronized?sponsor=1" + }, + "peerDependencies": { + "prettier": "^3.0.0" + } + }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -3969,16 +4023,17 @@ } }, "node_modules/@solarity/hardhat-migrate": { - "version": "2.0.0-alpha.14", - "resolved": "https://registry.npmjs.org/@solarity/hardhat-migrate/-/hardhat-migrate-2.0.0-alpha.14.tgz", - "integrity": "sha512-nNHyTsmfFXSrg0juO4N94PGB3LMs3s7OSFfJ6/t63ihA8JxDJVqK0I+PKMpgbmNncTgUetsocM83rkHzmJT+ow==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@solarity/hardhat-migrate/-/hardhat-migrate-2.0.1.tgz", + "integrity": "sha512-i65SQ9frU8SKJmCFBH8peJKI3aBGRs8xcl6uSMJuGz1cD0EUPI5JveAVc7YzJMZEsStQlItvwW1uNvCoH52AUw==", "dev": true, "hasInstallScript": true, "dependencies": { - "@nomicfoundation/hardhat-ethers": "3.0.4", + "@nomicfoundation/hardhat-ethers": "3.0.5", "@nomicfoundation/hardhat-verify": "1.1.1", "@nomiclabs/hardhat-truffle5": "2.0.7", "axios": "1.5.0", + "ethers": "6.9.0", "ora": "5.4.1" }, "peerDependencies": { @@ -6919,8 +6974,7 @@ "version": "4.0.0-beta.5", "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-4.0.0-beta.5.tgz", "integrity": "sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==", - "dev": true, - "peer": true + "dev": true }, "node_modules/agent-base": { "version": "6.0.2", @@ -7090,6 +7144,15 @@ "node": ">=4" } }, + "node_modules/antlr4": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/antlr4/-/antlr4-4.13.1.tgz", + "integrity": "sha512-kiXTspaRYvnIArgE97z5YVVf/cDVQABr3abFRR6mE7yesLMkgu4ujuyV/sgxafQ8wgve0DJQUJ38Z8tkgA2izA==", + "dev": true, + "engines": { + "node": ">=16" + } + }, "node_modules/antlr4ts": { "version": "0.5.0-alpha.4", "resolved": "https://registry.npmjs.org/antlr4ts/-/antlr4ts-0.5.0-alpha.4.tgz", @@ -7579,6 +7642,12 @@ "node": "*" } }, + "node_modules/ast-parents": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/ast-parents/-/ast-parents-0.0.1.tgz", + "integrity": "sha512-XHusKxKz3zoYk1ic8Un640joHbFMhbqneyoZfoKnEGtf2ey9Uh/IdpcQplODdO/kENaMIWsD0nJm4+wX3UNLHA==", + "dev": true + }, "node_modules/astral-regex": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", @@ -8713,6 +8782,16 @@ "node": ">=0.10.0" } }, + "node_modules/config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "dev": true, + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, "node_modules/console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", @@ -9030,6 +9109,32 @@ "node": ">= 0.10" } }, + "node_modules/cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "dev": true, + "dependencies": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/crc-32": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", @@ -10943,9 +11048,9 @@ } }, "node_modules/ethers": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.7.1.tgz", - "integrity": "sha512-qX5kxIFMfg1i+epfgb0xF4WM7IqapIIu50pOJ17aebkxxa4BacW5jFrQRmCJpDEg2ZK2oNtR5QjrQ1WDBF29dA==", + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.9.0.tgz", + "integrity": "sha512-pmfNyQzc2mseLe91FnT2vmNaTt8dDzhxZ/xItAV7uGsF4dI4ek2ufMu3rAkgQETL/TIs0GS5A+U05g9QyWnv3Q==", "dev": true, "funding": [ { @@ -10957,11 +11062,10 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "peer": true, "dependencies": { - "@adraffy/ens-normalize": "1.9.2", - "@noble/hashes": "1.1.2", - "@noble/secp256k1": "1.7.1", + "@adraffy/ens-normalize": "1.10.0", + "@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", @@ -10971,25 +11075,23 @@ "node": ">=14.0.0" } }, - "node_modules/ethers/node_modules/@noble/hashes": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.1.2.tgz", - "integrity": "sha512-KYRCASVTv6aeUi1tsF8/vpyR7zpfs3FUzy2Jqm+MU+LmUKhQ0y2FpfwqkCcxSg2ua4GALJd8k2R76WxwZGbQpA==", + "node_modules/ethers/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, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "peer": true + "dependencies": { + "@noble/hashes": "1.3.2" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } }, "node_modules/ethers/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, - "peer": true + "dev": true }, "node_modules/ethjs-unit": { "version": "0.1.6", @@ -17680,6 +17782,12 @@ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, "node_modules/json-pointer": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/json-pointer/-/json-pointer-0.6.2.tgz", @@ -17846,6 +17954,21 @@ "graceful-fs": "^4.1.11" } }, + "node_modules/latest-version": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-7.0.0.tgz", + "integrity": "sha512-KvNT4XqAMzdcL6ka6Tl3i2lYeFDgXNCuIX+xNx6ZMVR1dFq+idXd9FLKNMOIx0t9mJ9/HudyX4oZWXZQ0UJHeg==", + "dev": true, + "dependencies": { + "package-json": "^8.1.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/lcid": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", @@ -18184,6 +18307,12 @@ "node": ">= 0.8.0" } }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, "node_modules/load-json-file": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", @@ -19685,6 +19814,24 @@ "node": ">=4" } }, + "node_modules/package-json": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-8.1.1.tgz", + "integrity": "sha512-cbH9IAIJHNj9uXi196JVsRlt7cHKak6u/e6AkL/bkRelZ7rlL3X1YKxsZwa36xipOEKAsdtmaG6aAJoM1fx2zA==", + "dev": true, + "dependencies": { + "got": "^12.1.0", + "registry-auth-token": "^5.0.1", + "registry-url": "^6.0.0", + "semver": "^7.3.7" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", @@ -19723,6 +19870,24 @@ "integrity": "sha512-ft3iAoLOB/MlwbNXgzy43SWGP6sQki2jQvAyBg/zDFAgr9bfNWZIUj42Kw2eJIl8kEi4PbgE6U1Zau/HwI75HA==", "dev": true }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/parse5": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", @@ -20288,8 +20453,6 @@ "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", "dev": true, - "optional": true, - "peer": true, "engines": { "node": ">=4" } @@ -20773,14 +20936,14 @@ } }, "node_modules/prettier-plugin-solidity": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/prettier-plugin-solidity/-/prettier-plugin-solidity-1.2.0.tgz", - "integrity": "sha512-fgxcUZpVAP+LlRfy5JI5oaAkXGkmsje2VJ5krv/YMm+rcTZbIUwFguSw5f+WFuttMjpDm6wB4UL7WVkArEfiVA==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/prettier-plugin-solidity/-/prettier-plugin-solidity-1.3.1.tgz", + "integrity": "sha512-MN4OP5I2gHAzHZG1wcuJl0FsLS3c4Cc5494bbg+6oQWBPuEamjwDvmGfFMZ6NFzsh3Efd9UUxeT7ImgjNH4ozA==", "dev": true, "dependencies": { - "@solidity-parser/parser": "^0.16.2", + "@solidity-parser/parser": "^0.17.0", "semver": "^7.5.4", - "solidity-comments-extractor": "^0.0.7" + "solidity-comments-extractor": "^0.0.8" }, "engines": { "node": ">=16" @@ -20790,13 +20953,16 @@ } }, "node_modules/prettier-plugin-solidity/node_modules/@solidity-parser/parser": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.16.2.tgz", - "integrity": "sha512-PI9NfoA3P8XK2VBkK5oIfRgKDsicwDZfkVq9ZTBCQYGOP1N2owgY2dyLGyU5/J/hQs8KRk55kdmvTLjy3Mu3vg==", - "dev": true, - "dependencies": { - "antlr4ts": "^0.5.0-alpha.4" - } + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.17.0.tgz", + "integrity": "sha512-Nko8R0/kUo391jsEHHxrGM07QFdnPGvlmox4rmH0kNiNAashItAilhy4Mv4pK5gQmW5f4sXAF58fwJbmlkGcVw==", + "dev": true + }, + "node_modules/prettier-plugin-solidity/node_modules/solidity-comments-extractor": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/solidity-comments-extractor/-/solidity-comments-extractor-0.0.8.tgz", + "integrity": "sha512-htM7Vn6LhHreR+EglVMd2s+sZhcXAirB1Zlyrv5zBuTxieCvjfnRpd7iZk75m/u6NOlEyQ94C6TWbBn2cY7w8g==", + "dev": true }, "node_modules/process": { "version": "0.11.10", @@ -20847,6 +21013,12 @@ "node": ">= 4" } }, + "node_modules/proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "dev": true + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -21084,8 +21256,7 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "optional": true, - "peer": true, + "devOptional": true, "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", @@ -21100,8 +21271,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", - "optional": true, - "peer": true, + "devOptional": true, "engines": { "node": ">=0.10.0" } @@ -21282,6 +21452,33 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/registry-auth-token": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.0.2.tgz", + "integrity": "sha512-o/3ikDxtXaA59BmZuZrJZDJv8NMDGSj+6j6XaeBmHw8eY1i1qd9+6H+LjVvQXx3HN6aRCGa1cUdJ9RaJZUugnQ==", + "dev": true, + "dependencies": { + "@pnpm/npm-conf": "^2.1.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/registry-url": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-6.0.1.tgz", + "integrity": "sha512-+crtS5QjFRqFCoQmvGduwYWEBng99ZvmFvF+cUJkGYF1L1BfU8C6Zp9T7f5vPAwyLkUExpvK+ANVZmGU49qi4Q==", + "dev": true, + "dependencies": { + "rc": "1.2.8" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/req-cwd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/req-cwd/-/req-cwd-2.0.0.tgz", @@ -22563,6 +22760,217 @@ "lodash.assign": "^4.0.6" } }, + "node_modules/solhint": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/solhint/-/solhint-4.1.1.tgz", + "integrity": "sha512-7G4iF8H5hKHc0tR+/uyZesSKtfppFIMvPSW+Ku6MSL25oVRuyFeqNhOsXHfkex64wYJyXs4fe+pvhB069I19Tw==", + "dev": true, + "dependencies": { + "@solidity-parser/parser": "^0.16.0", + "ajv": "^6.12.6", + "antlr4": "^4.11.0", + "ast-parents": "^0.0.1", + "chalk": "^4.1.2", + "commander": "^10.0.0", + "cosmiconfig": "^8.0.0", + "fast-diff": "^1.2.0", + "glob": "^8.0.3", + "ignore": "^5.2.4", + "js-yaml": "^4.1.0", + "latest-version": "^7.0.0", + "lodash": "^4.17.21", + "pluralize": "^8.0.0", + "semver": "^7.5.2", + "strip-ansi": "^6.0.1", + "table": "^6.8.1", + "text-table": "^0.2.0" + }, + "bin": { + "solhint": "solhint.js" + }, + "optionalDependencies": { + "prettier": "^2.8.3" + } + }, + "node_modules/solhint-plugin-prettier": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/solhint-plugin-prettier/-/solhint-plugin-prettier-0.1.0.tgz", + "integrity": "sha512-SDOTSM6tZxZ6hamrzl3GUgzF77FM6jZplgL2plFBclj/OjKP8Z3eIPojKU73gRr0MvOS8ACZILn8a5g0VTz/Gw==", + "dev": true, + "dependencies": { + "@prettier/sync": "^0.3.0", + "prettier-linter-helpers": "^1.0.0" + }, + "peerDependencies": { + "prettier": "^3.0.0", + "prettier-plugin-solidity": "^1.0.0" + } + }, + "node_modules/solhint/node_modules/@solidity-parser/parser": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.16.2.tgz", + "integrity": "sha512-PI9NfoA3P8XK2VBkK5oIfRgKDsicwDZfkVq9ZTBCQYGOP1N2owgY2dyLGyU5/J/hQs8KRk55kdmvTLjy3Mu3vg==", + "dev": true, + "dependencies": { + "antlr4ts": "^0.5.0-alpha.4" + } + }, + "node_modules/solhint/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/solhint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/solhint/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/solhint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/solhint/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/solhint/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/solhint/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/solhint/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/solhint/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/solhint/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/solhint/node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true, + "optional": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/solhint/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/solhint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/solidity-ast": { "version": "0.4.49", "resolved": "https://registry.npmjs.org/solidity-ast/-/solidity-ast-0.4.49.tgz", @@ -25794,11 +26202,10 @@ "dev": true }, "@adraffy/ens-normalize": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.9.2.tgz", - "integrity": "sha512-0h+FrQDqe2Wn+IIGFkTCd4aAwTJ+7834Ek1COohCyV26AXhwQ7WQaz+4F/nLOeVl/3BtWHOHLPsq46V8YB46Eg==", - "dev": true, - "peer": true + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.0.tgz", + "integrity": "sha512-nA9XHtlAkYfJxY7bce8DcN7eKxWWCWkU+1GR9d+U6MbNpfwQp8TI7vqOsBsMcHoT4mBu2kypKoSKnghEzOOq5Q==", + "dev": true }, "@aduh95/viz.js": { "version": "3.7.0", @@ -28052,9 +28459,9 @@ } }, "@nomicfoundation/hardhat-ethers": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-ethers/-/hardhat-ethers-3.0.4.tgz", - "integrity": "sha512-k9qbLoY7qn6C6Y1LI0gk2kyHXil2Tauj4kGzQ8pgxYXIGw8lWn8tuuL72E11CrlKaXRUvOgF0EXrv/msPI2SbA==", + "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, "requires": { "debug": "^4.1.1", @@ -28471,6 +28878,47 @@ } } }, + "@pnpm/config.env-replace": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz", + "integrity": "sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==", + "dev": true + }, + "@pnpm/network.ca-file": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@pnpm/network.ca-file/-/network.ca-file-1.0.2.tgz", + "integrity": "sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==", + "dev": true, + "requires": { + "graceful-fs": "4.2.10" + }, + "dependencies": { + "graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + } + } + }, + "@pnpm/npm-conf": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@pnpm/npm-conf/-/npm-conf-2.2.2.tgz", + "integrity": "sha512-UA91GwWPhFExt3IizW6bOeY/pQ0BkuNwKjk9iQW9KqxluGCrg4VenZ0/L+2Y0+ZOtme72EVvg6v0zo3AMQRCeA==", + "dev": true, + "requires": { + "@pnpm/config.env-replace": "^1.1.0", + "@pnpm/network.ca-file": "^1.0.1", + "config-chain": "^1.1.11" + } + }, + "@prettier/sync": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@prettier/sync/-/sync-0.3.0.tgz", + "integrity": "sha512-3dcmCyAxIcxy036h1I7MQU/uEEBq8oLwf1CE3xeze+MPlgkdlb/+w6rGR/1dhp6Hqi17fRS6nvwnOzkESxEkOw==", + "dev": true, + "requires": {} + }, "@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -28831,15 +29279,16 @@ } }, "@solarity/hardhat-migrate": { - "version": "2.0.0-alpha.14", - "resolved": "https://registry.npmjs.org/@solarity/hardhat-migrate/-/hardhat-migrate-2.0.0-alpha.14.tgz", - "integrity": "sha512-nNHyTsmfFXSrg0juO4N94PGB3LMs3s7OSFfJ6/t63ihA8JxDJVqK0I+PKMpgbmNncTgUetsocM83rkHzmJT+ow==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@solarity/hardhat-migrate/-/hardhat-migrate-2.0.1.tgz", + "integrity": "sha512-i65SQ9frU8SKJmCFBH8peJKI3aBGRs8xcl6uSMJuGz1cD0EUPI5JveAVc7YzJMZEsStQlItvwW1uNvCoH52AUw==", "dev": true, "requires": { - "@nomicfoundation/hardhat-ethers": "3.0.4", + "@nomicfoundation/hardhat-ethers": "3.0.5", "@nomicfoundation/hardhat-verify": "1.1.1", "@nomiclabs/hardhat-truffle5": "2.0.7", "axios": "1.5.0", + "ethers": "6.9.0", "ora": "5.4.1" } }, @@ -31278,8 +31727,7 @@ "version": "4.0.0-beta.5", "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-4.0.0-beta.5.tgz", "integrity": "sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==", - "dev": true, - "peer": true + "dev": true }, "agent-base": { "version": "6.0.2", @@ -31410,6 +31858,12 @@ "color-convert": "^1.9.0" } }, + "antlr4": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/antlr4/-/antlr4-4.13.1.tgz", + "integrity": "sha512-kiXTspaRYvnIArgE97z5YVVf/cDVQABr3abFRR6mE7yesLMkgu4ujuyV/sgxafQ8wgve0DJQUJ38Z8tkgA2izA==", + "dev": true + }, "antlr4ts": { "version": "0.5.0-alpha.4", "resolved": "https://registry.npmjs.org/antlr4ts/-/antlr4ts-0.5.0-alpha.4.tgz", @@ -31811,6 +32265,12 @@ "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", "dev": true }, + "ast-parents": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/ast-parents/-/ast-parents-0.0.1.tgz", + "integrity": "sha512-XHusKxKz3zoYk1ic8Un640joHbFMhbqneyoZfoKnEGtf2ey9Uh/IdpcQplODdO/kENaMIWsD0nJm4+wX3UNLHA==", + "dev": true + }, "astral-regex": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", @@ -32713,6 +33173,16 @@ } } }, + "config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "dev": true, + "requires": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, "console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", @@ -32945,6 +33415,18 @@ "vary": "^1" } }, + "cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "dev": true, + "requires": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + } + }, "crc-32": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", @@ -34419,34 +34901,34 @@ } }, "ethers": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.7.1.tgz", - "integrity": "sha512-qX5kxIFMfg1i+epfgb0xF4WM7IqapIIu50pOJ17aebkxxa4BacW5jFrQRmCJpDEg2ZK2oNtR5QjrQ1WDBF29dA==", + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.9.0.tgz", + "integrity": "sha512-pmfNyQzc2mseLe91FnT2vmNaTt8dDzhxZ/xItAV7uGsF4dI4ek2ufMu3rAkgQETL/TIs0GS5A+U05g9QyWnv3Q==", "dev": true, - "peer": true, "requires": { - "@adraffy/ens-normalize": "1.9.2", - "@noble/hashes": "1.1.2", - "@noble/secp256k1": "1.7.1", + "@adraffy/ens-normalize": "1.10.0", + "@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" }, "dependencies": { - "@noble/hashes": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.1.2.tgz", - "integrity": "sha512-KYRCASVTv6aeUi1tsF8/vpyR7zpfs3FUzy2Jqm+MU+LmUKhQ0y2FpfwqkCcxSg2ua4GALJd8k2R76WxwZGbQpA==", + "@noble/curves": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", + "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", "dev": true, - "peer": true + "requires": { + "@noble/hashes": "1.3.2" + } }, "@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, - "peer": true + "dev": true } } }, @@ -39672,6 +40154,12 @@ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, "json-pointer": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/json-pointer/-/json-pointer-0.6.2.tgz", @@ -39813,6 +40301,15 @@ "graceful-fs": "^4.1.11" } }, + "latest-version": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-7.0.0.tgz", + "integrity": "sha512-KvNT4XqAMzdcL6ka6Tl3i2lYeFDgXNCuIX+xNx6ZMVR1dFq+idXd9FLKNMOIx0t9mJ9/HudyX4oZWXZQ0UJHeg==", + "dev": true, + "requires": { + "package-json": "^8.1.0" + } + }, "lcid": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", @@ -40078,6 +40575,12 @@ "type-check": "~0.3.2" } }, + "lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, "load-json-file": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", @@ -41253,6 +41756,18 @@ "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==" }, + "package-json": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-8.1.1.tgz", + "integrity": "sha512-cbH9IAIJHNj9uXi196JVsRlt7cHKak6u/e6AkL/bkRelZ7rlL3X1YKxsZwa36xipOEKAsdtmaG6aAJoM1fx2zA==", + "dev": true, + "requires": { + "got": "^12.1.0", + "registry-auth-token": "^5.0.1", + "registry-url": "^6.0.0", + "semver": "^7.3.7" + } + }, "pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", @@ -41288,6 +41803,18 @@ "integrity": "sha512-ft3iAoLOB/MlwbNXgzy43SWGP6sQki2jQvAyBg/zDFAgr9bfNWZIUj42Kw2eJIl8kEi4PbgE6U1Zau/HwI75HA==", "dev": true }, + "parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + } + }, "parse5": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", @@ -41702,9 +42229,7 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", - "dev": true, - "optional": true, - "peer": true + "dev": true }, "pollock": { "version": "0.2.1", @@ -42134,24 +42659,27 @@ } }, "prettier-plugin-solidity": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/prettier-plugin-solidity/-/prettier-plugin-solidity-1.2.0.tgz", - "integrity": "sha512-fgxcUZpVAP+LlRfy5JI5oaAkXGkmsje2VJ5krv/YMm+rcTZbIUwFguSw5f+WFuttMjpDm6wB4UL7WVkArEfiVA==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/prettier-plugin-solidity/-/prettier-plugin-solidity-1.3.1.tgz", + "integrity": "sha512-MN4OP5I2gHAzHZG1wcuJl0FsLS3c4Cc5494bbg+6oQWBPuEamjwDvmGfFMZ6NFzsh3Efd9UUxeT7ImgjNH4ozA==", "dev": true, "requires": { - "@solidity-parser/parser": "^0.16.2", + "@solidity-parser/parser": "^0.17.0", "semver": "^7.5.4", - "solidity-comments-extractor": "^0.0.7" + "solidity-comments-extractor": "^0.0.8" }, "dependencies": { "@solidity-parser/parser": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.16.2.tgz", - "integrity": "sha512-PI9NfoA3P8XK2VBkK5oIfRgKDsicwDZfkVq9ZTBCQYGOP1N2owgY2dyLGyU5/J/hQs8KRk55kdmvTLjy3Mu3vg==", - "dev": true, - "requires": { - "antlr4ts": "^0.5.0-alpha.4" - } + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.17.0.tgz", + "integrity": "sha512-Nko8R0/kUo391jsEHHxrGM07QFdnPGvlmox4rmH0kNiNAashItAilhy4Mv4pK5gQmW5f4sXAF58fwJbmlkGcVw==", + "dev": true + }, + "solidity-comments-extractor": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/solidity-comments-extractor/-/solidity-comments-extractor-0.0.8.tgz", + "integrity": "sha512-htM7Vn6LhHreR+EglVMd2s+sZhcXAirB1Zlyrv5zBuTxieCvjfnRpd7iZk75m/u6NOlEyQ94C6TWbBn2cY7w8g==", + "dev": true } } }, @@ -42197,6 +42725,12 @@ } } }, + "proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "dev": true + }, "proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -42363,8 +42897,7 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "optional": true, - "peer": true, + "devOptional": true, "requires": { "deep-extend": "^0.6.0", "ini": "~1.3.0", @@ -42376,8 +42909,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", - "optional": true, - "peer": true + "devOptional": true } } }, @@ -42522,6 +43054,24 @@ "set-function-name": "^2.0.0" } }, + "registry-auth-token": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.0.2.tgz", + "integrity": "sha512-o/3ikDxtXaA59BmZuZrJZDJv8NMDGSj+6j6XaeBmHw8eY1i1qd9+6H+LjVvQXx3HN6aRCGa1cUdJ9RaJZUugnQ==", + "dev": true, + "requires": { + "@pnpm/npm-conf": "^2.1.0" + } + }, + "registry-url": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-6.0.1.tgz", + "integrity": "sha512-+crtS5QjFRqFCoQmvGduwYWEBng99ZvmFvF+cUJkGYF1L1BfU8C6Zp9T7f5vPAwyLkUExpvK+ANVZmGU49qi4Q==", + "dev": true, + "requires": { + "rc": "1.2.8" + } + }, "req-cwd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/req-cwd/-/req-cwd-2.0.0.tgz", @@ -43530,6 +44080,162 @@ } } }, + "solhint": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/solhint/-/solhint-4.1.1.tgz", + "integrity": "sha512-7G4iF8H5hKHc0tR+/uyZesSKtfppFIMvPSW+Ku6MSL25oVRuyFeqNhOsXHfkex64wYJyXs4fe+pvhB069I19Tw==", + "dev": true, + "requires": { + "@solidity-parser/parser": "^0.16.0", + "ajv": "^6.12.6", + "antlr4": "^4.11.0", + "ast-parents": "^0.0.1", + "chalk": "^4.1.2", + "commander": "^10.0.0", + "cosmiconfig": "^8.0.0", + "fast-diff": "^1.2.0", + "glob": "^8.0.3", + "ignore": "^5.2.4", + "js-yaml": "^4.1.0", + "latest-version": "^7.0.0", + "lodash": "^4.17.21", + "pluralize": "^8.0.0", + "prettier": "^2.8.3", + "semver": "^7.5.2", + "strip-ansi": "^6.0.1", + "table": "^6.8.1", + "text-table": "^0.2.0" + }, + "dependencies": { + "@solidity-parser/parser": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.16.2.tgz", + "integrity": "sha512-PI9NfoA3P8XK2VBkK5oIfRgKDsicwDZfkVq9ZTBCQYGOP1N2owgY2dyLGyU5/J/hQs8KRk55kdmvTLjy3Mu3vg==", + "dev": true, + "requires": { + "antlr4ts": "^0.5.0-alpha.4" + } + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true + }, + "glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true, + "optional": true + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "solhint-plugin-prettier": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/solhint-plugin-prettier/-/solhint-plugin-prettier-0.1.0.tgz", + "integrity": "sha512-SDOTSM6tZxZ6hamrzl3GUgzF77FM6jZplgL2plFBclj/OjKP8Z3eIPojKU73gRr0MvOS8ACZILn8a5g0VTz/Gw==", + "dev": true, + "requires": { + "@prettier/sync": "^0.3.0", + "prettier-linter-helpers": "^1.0.0" + } + }, "solidity-ast": { "version": "0.4.49", "resolved": "https://registry.npmjs.org/solidity-ast/-/solidity-ast-0.4.49.tgz", diff --git a/package.json b/package.json index 4314638..d899248 100644 --- a/package.json +++ b/package.json @@ -9,21 +9,21 @@ "compile": "npx hardhat compile", "coverage": "npx hardhat coverage --solcoverjs ./.solcover.ts", "clean": "npx hardhat clean", - "test": "npx hardhat test", - "test-without-fork": "npx hardhat test $(find './test' -type f -iname '*test.ts' -not -path './test/fork/*')", + "test": "npx hardhat test $(find './test' -type f -iname '*test.ts' -not -path './test/fork/*')", + "test-fork": "npx hardhat test $(find './test' -type f -iname '*test.ts' -path './test/fork/*')", "test-all": "npm run deploy-localhost && npm run test", "private-network": "npx hardhat node", "private-network-fork": "npx hardhat node --fork https://mainnet.infura.io/v3/$(grep INFURA_KEY .env | cut -d '\"' -f2)", "deploy-localhost": "npx hardhat migrate --network localhost", "deploy-goerli": "npx hardhat migrate --network goerli --verify", - "deploy-sepolia": "npx hardhat migrate --network sepolia --verify --continue", - "deploy-chapel": "npx hardhat migrate --network chapel --verify --confirmations 1", - "deploy-mumbai": "npx hardhat migrate --network mumbai --verify --confirmations 10", - "deploy-fuji": "npx hardhat migrate --network fuji --verify --confirmations 5", - "deploy-ethereum": "npx hardhat migrate --network ethereum --verify --confirmations 4", - "deploy-bsc": "npx hardhat migrate --network bsc --verify --confirmations 2", - "deploy-polygon": "npx hardhat migrate --network polygon --verify --confirmations 10", - "deploy-avalanche": "npx hardhat migrate --network avalanche --verify --confirmations 5", + "deploy-sepolia": "npx hardhat migrate --network sepolia --verify", + "deploy-chapel": "npx hardhat migrate --network chapel --verify", + "deploy-mumbai": "npx hardhat migrate --network mumbai --verify", + "deploy-fuji": "npx hardhat migrate --network fuji --verify", + "deploy-ethereum": "npx hardhat migrate --network ethereum --verify", + "deploy-bsc": "npx hardhat migrate --network bsc --verify", + "deploy-polygon": "npx hardhat migrate --network polygon --verify", + "deploy-avalanche": "npx hardhat migrate --network avalanche --verify", "deploy-arbitrum-goerli": "npx hardhat migrate --network arbitrum_goerli --verify", "deploy-arbitrum-sepolia": "npx hardhat migrate --network arbitrum_sepolia --verify", "generate-types": "TYPECHAIN_FORCE=true npx hardhat typechain", @@ -31,7 +31,10 @@ "lint-fix": "npm run lint-sol-fix && npm run lint-ts-fix && npm run lint-json-fix", "lint-json-fix": "prettier --write \"./**/*.json\"", "lint-ts-fix": "prettier --write \"./**/*.ts\"", - "lint-sol-fix": "prettier --write \"contracts/**/*.sol\"" + "lint-sol-fix": "prettier --write \"contracts/**/*.sol\"", + "lint": "npm run lint-ts && npm run lint-sol", + "lint-ts": "eslint \"./**/*.ts\"", + "lint-sol": "solhint \"contracts/**/*.sol\"" }, "dependencies": { "@arbitrum/token-bridge-contracts": "^1.1.2", @@ -54,7 +57,7 @@ "@nomiclabs/hardhat-truffle5": "^2.0.7", "@nomiclabs/hardhat-web3": "^2.0.0", "@solarity/hardhat-markup": "^1.0.2", - "@solarity/hardhat-migrate": "2.0.0-alpha.14", + "@solarity/hardhat-migrate": "2.0.1", "@trivago/prettier-plugin-sort-imports": "^4.3.0", "@typechain/ethers-v6": "^0.5.0", "@typechain/hardhat": "^9.0.0", @@ -74,7 +77,9 @@ "husky": "^8.0.2", "mocha": "^10.2.0", "prettier": "^3.1.0", - "prettier-plugin-solidity": "^1.2.0", + "prettier-plugin-solidity": "^1.3.1", + "solhint": "^4.0.0", + "solhint-plugin-prettier": "^0.1.0", "solidity-coverage": "^0.8.5", "ts-node": "^10.9.1", "tsconfig-paths": "^4.2.0", diff --git a/scripts/retryPayload.ts b/scripts/retryPayload.ts new file mode 100644 index 0000000..fcbee1c --- /dev/null +++ b/scripts/retryPayload.ts @@ -0,0 +1,41 @@ +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/tryAddLiquidity.ts b/scripts/tryAddLiquidity.ts index 5db5d6c..69a17a8 100644 --- a/scripts/tryAddLiquidity.ts +++ b/scripts/tryAddLiquidity.ts @@ -20,7 +20,7 @@ async function main() { console.log(`wsteth balance of ${l2MessageReceiverAddress}: ${wstethBalance}`); console.log(`mor balance of ${l2MessageReceiverAddress}: ${morBalance}`); - const tx = await l2TokenReceiver.increaseLiquidityCurrentRange(86416, wstethBalance, morBalance); + const tx = await l2TokenReceiver.increaseLiquidityCurrentRange(86416, wstethBalance, morBalance, 0, 0); await tx.wait(); console.log('liquidity added'); diff --git a/scripts/tryTokensSwap.ts b/scripts/tryTokensSwap.ts index 8f2ad75..ca822b4 100644 --- a/scripts/tryTokensSwap.ts +++ b/scripts/tryTokensSwap.ts @@ -2,16 +2,6 @@ import { ethers } from 'hardhat'; import { L2TokenReceiver } from '@/generated-types/ethers'; -async function setParams(l2TokenReceiver: L2TokenReceiver) { - await l2TokenReceiver.editParams({ - tokenIn: '0x87726993938107d9B9ce08c99BDde8736D899a5D', - tokenOut: '0xCF84E18F1a2803C15675622B24600910dc2a1E13', - fee: 10000, - sqrtPriceLimitX96: 0, - }); - console.log(await l2TokenReceiver.params()); -} - async function main() { const L2TokenReceiver = await ethers.getContractFactory('L2TokenReceiver'); const l2TokenReceiver = L2TokenReceiver.attach('0x2C0f43E5C92459F62C102517956A95E88E177e95') as L2TokenReceiver; diff --git a/scripts/utils/constants.ts b/scripts/utils/constants.ts index f1c6d9e..73c70bc 100644 --- a/scripts/utils/constants.ts +++ b/scripts/utils/constants.ts @@ -1,6 +1,8 @@ export const ZERO_ADDR = '0x0000000000000000000000000000000000000000'; export const ETHER_ADDR = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE'; +export const ZERO_BYTES32 = '0x0000000000000000000000000000000000000000000000000000000000000000'; + export const SECONDS_IN_DAY = 86400; export const SECONDS_IN_MONTH = SECONDS_IN_DAY * 30; diff --git a/test/Distribution.test.ts b/test/Distribution.test.ts index 0c6c8bc..d063234 100644 --- a/test/Distribution.test.ts +++ b/test/Distribution.test.ts @@ -8,6 +8,7 @@ import { Distribution__factory, GatewayRouterMock, IDistribution, + IL1Sender, L1Sender, L2MessageReceiver, L2TokenReceiver, @@ -90,6 +91,9 @@ describe('Distribution', () => { let gatewayRouter: GatewayRouterMock; let swapRouter: SwapRouterMock; let nonfungiblePositionManager: NonfungiblePositionManagerMock; + let l2TokenReceiverImplementation: L2TokenReceiver; + let l2MessageReceiverImplementation: L2MessageReceiver; + let l1SenderImplementation: L1Sender; // START deploy contracts without deps [ lib, @@ -97,62 +101,70 @@ describe('Distribution', () => { lZEndpointMockSender, lZEndpointMockReceiver, gatewayRouter, - l2MessageReceiver, swapRouter, nonfungiblePositionManager, + l2TokenReceiverImplementation, + l2MessageReceiverImplementation, + l1SenderImplementation, ] = await Promise.all([ libFactory.deploy(), stETHMockFactory.deploy(), LZEndpointMock.deploy(senderChainId), LZEndpointMock.deploy(receiverChainId), gatewayRouterMock.deploy(), - L2MessageReceiver.deploy(), SwapRouterMock.deploy(), NonfungiblePositionManagerMock.deploy(), + L2TokenReceiver.deploy(), + L2MessageReceiver.deploy(), + l1SenderFactory.deploy(), ]); + + distributionFactory = await ethers.getContractFactory('Distribution', { + libraries: { + LinearDistributionIntervalDecrease: await lib.getAddress(), + }, + }); + const distributionImplementation = await distributionFactory.deploy(); // END wstETH = await wstETHMockFactory.deploy(depositToken); - l2TokenReceiver = await L2TokenReceiver.deploy(swapRouter, nonfungiblePositionManager, { + 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 L2TokenReceiver; + await l2TokenReceiver.L2TokenReceiver__init(swapRouter, nonfungiblePositionManager, { tokenIn: depositToken, tokenOut: depositToken, fee: 3000, sqrtPriceLimitX96: 0, }); - l1Sender = await l1SenderFactory.deploy(); - - await l1Sender.setDepositTokenConfig({ - token: wstETH, - gateway: gatewayRouter, - receiver: l2TokenReceiver, - }); - - await l1Sender.setRewardTokenConfig({ - gateway: lZEndpointMockSender, - receiver: l2MessageReceiver, - receiverChainId: receiverChainId, - }); - // START deploy distribution contract - distributionFactory = await ethers.getContractFactory('Distribution', { - libraries: { - LinearDistributionIntervalDecrease: await lib.getAddress(), - }, - }); - - const distributionImplementation = await distributionFactory.deploy(); const distributionProxy = await ERC1967ProxyFactory.deploy(await distributionImplementation.getAddress(), '0x'); distribution = distributionFactory.attach(await distributionProxy.getAddress()) as Distribution; // END - // Get contract addresses - const [distributionAddress] = await Promise.all([distribution.getAddress()]); + const rewardTokenConfig: IL1Sender.RewardTokenConfigStruct = { + gateway: lZEndpointMockSender, + receiver: l2MessageReceiver, + receiverChainId: receiverChainId, + }; + const depositTokenConfig: IL1Sender.DepositTokenConfigStruct = { + token: wstETH, + gateway: gatewayRouter, + receiver: l2TokenReceiver, + }; + + const l1SenderProxy = await ERC1967ProxyFactory.deploy(l1SenderImplementation, '0x'); + l1Sender = l1SenderFactory.attach(l1SenderProxy) as L1Sender; + await l1Sender.L1Sender__init(distribution, rewardTokenConfig, depositTokenConfig); // Deploy reward token rewardToken = await MORFactory.deploy(wei(1000000000)); - rewardToken.transferOwnership(l2MessageReceiver); + await rewardToken.transferOwnership(l2MessageReceiver); await l2MessageReceiver.setParams(rewardToken, { gateway: lZEndpointMockReceiver, @@ -166,11 +178,10 @@ describe('Distribution', () => { await Promise.all([depositToken.mint(ownerAddress, wei(1000)), depositToken.mint(secondAddress, wei(1000))]); await Promise.all([ - depositToken.approve(distributionAddress, wei(1000)), - depositToken.connect(SECOND).approve(distributionAddress, wei(1000)), + depositToken.approve(distribution, wei(1000)), + depositToken.connect(SECOND).approve(distribution, wei(1000)), ]); - - await l1Sender.transferOwnership(distributionAddress); + await l1Sender.transferOwnership(distribution); await reverter.snapshot(); }); @@ -241,6 +252,16 @@ describe('Distribution', () => { it('should create pool with correct data', async () => { const pool = getDefaultPool(); + const tx = await distribution.createPool(pool); + await expect(tx).to.emit(distribution, 'PoolCreated'); + + const poolData: IDistribution.PoolStruct = await distribution.pools(0); + expect(_comparePoolStructs(pool, poolData)).to.be.true; + }); + it('should correctly pool with constant reward', async () => { + const pool = getDefaultPool(); + pool.rewardDecrease = 0; + await distribution.createPool(pool); const poolData: IDistribution.PoolStruct = await distribution.pools(0); @@ -279,7 +300,7 @@ describe('Distribution', () => { await distribution.createPool(getDefaultPool()); }); - it('should revert if try to change pool type', async () => { + it('should edit pool with correct data', async () => { const newPool = { ...defaultPool, payoutStart: 10 * oneDay, @@ -288,6 +309,18 @@ describe('Distribution', () => { initialReward: wei(111), rewardDecrease: wei(222), minimalStake: wei(333), + }; + + const tx = await distribution.editPool(poolId, newPool); + await expect(tx).to.emit(distribution, 'PoolEdited'); + + const poolData: IDistribution.PoolStruct = await distribution.pools(poolId); + expect(_comparePoolStructs(newPool, poolData)).to.be.true; + }); + + it('should revert if try to change pool type', async () => { + const newPool = { + ...defaultPool, isPublic: false, }; @@ -324,8 +357,10 @@ describe('Distribution', () => { it('should correctly imitate stake and withdraw process', async () => { let userData; - setNextTime(oneHour * 2); - await distribution.manageUsersInPrivatePool(poolId, [secondAddress, ownerAddress], [wei(1), wei(4)]); + await setNextTime(oneHour * 2); + let tx = await distribution.manageUsersInPrivatePool(poolId, [secondAddress, ownerAddress], [wei(1), wei(4)]); + await expect(tx).to.emit(distribution, 'UserStaked').withArgs(poolId, secondAddress, wei(1)); + await expect(tx).to.emit(distribution, 'UserStaked').withArgs(poolId, ownerAddress, wei(4)); expect(await depositToken.balanceOf(secondAddress)).to.eq(wei(1000)); expect(await rewardToken.balanceOf(secondAddress)).to.eq(wei(0)); @@ -339,8 +374,10 @@ describe('Distribution', () => { expect(userData.deposited).to.eq(wei(4)); expect(userData.pendingRewards).to.eq(0); - setNextTime(oneHour * 3); - await distribution.manageUsersInPrivatePool(poolId, [secondAddress, ownerAddress], [wei(10), wei(1)]); + await setNextTime(oneHour * 3); + tx = await distribution.manageUsersInPrivatePool(poolId, [secondAddress, ownerAddress], [wei(10), wei(1)]); + await expect(tx).to.emit(distribution, 'UserStaked').withArgs(poolId, secondAddress, wei(9)); + await expect(tx).to.emit(distribution, 'UserWithdrawn').withArgs(poolId, ownerAddress, wei(3)); expect(await depositToken.balanceOf(secondAddress)).to.eq(wei(1000)); expect(await rewardToken.balanceOf(secondAddress)).to.eq(wei(0)); @@ -357,7 +394,7 @@ describe('Distribution', () => { it('should correctly calculate and withdraw rewards', async () => { let userData; - setNextTime(oneHour * 2); + await setNextTime(oneHour * 2); await distribution.manageUsersInPrivatePool(poolId, [secondAddress, ownerAddress], [wei(1), wei(4)]); // Claim after 1 day @@ -378,7 +415,7 @@ describe('Distribution', () => { expect(userData.pendingRewards).to.eq(0); // Withdraw after 2 days - setNextTime(oneDay + oneDay * 2); + await setNextTime(oneDay + oneDay * 2); await distribution.manageUsersInPrivatePool(poolId, [secondAddress, ownerAddress], [wei(0), wei(0)]); expect(await depositToken.balanceOf(secondAddress)).to.eq(wei(1000)); @@ -398,11 +435,11 @@ describe('Distribution', () => { it('should correctly calculate rewards after partial stake', async () => { let userData; - setNextTime(oneHour * 2); + await setNextTime(oneHour * 2); await distribution.manageUsersInPrivatePool(poolId, [secondAddress, ownerAddress], [wei(1), wei(4)]); // Stake after 1 day - setNextTime(oneDay + oneDay * 1); + await setNextTime(oneDay + oneDay * 1); await distribution.manageUsersInPrivatePool(poolId, [secondAddress, ownerAddress], [wei(5), wei(5)]); expect(await depositToken.balanceOf(secondAddress)).to.eq(wei(1000)); @@ -458,7 +495,7 @@ describe('Distribution', () => { it('should correctly calculate rewards if change before distribution end and claim after', async () => { let userData; - setNextTime(oneDay + oneDay * 25); + await setNextTime(oneDay + oneDay * 25); await distribution.manageUsersInPrivatePool(poolId, [secondAddress, ownerAddress], [wei(1), wei(4)]); await setNextTime(oneDay * 20000); @@ -497,7 +534,7 @@ describe('Distribution', () => { it('should correctly calculate rewards if change both at and distribution end', async () => { let userData; - setNextTime(oneDay + oneDay * 25); + await setNextTime(oneDay + oneDay * 25); await distribution.manageUsersInPrivatePool(poolId, [secondAddress, ownerAddress], [wei(1), wei(4)]); await setNextTime(oneDay * 20000); @@ -518,7 +555,7 @@ describe('Distribution', () => { it('should correctly work if multiple changes in one block', async () => { let userData; - setNextTime(oneHour * 2); + await setNextTime(oneHour * 2); await ethers.provider.send('evm_setAutomine', [false]); @@ -553,7 +590,7 @@ describe('Distribution', () => { expect(userData.pendingRewards).to.eq(0); // Withdraw after 2 days - setNextTime(oneDay + oneDay * 2); + await setNextTime(oneDay + oneDay * 2); await distribution.manageUsersInPrivatePool(poolId, [secondAddress, ownerAddress], [wei(0), wei(0)]); await distribution.claim(poolId, SECOND, { value: wei(0.5) }); await distribution.claim(poolId, OWNER, { value: wei(0.5) }); @@ -573,7 +610,7 @@ describe('Distribution', () => { it('should do nothing id deposited amount is the same', async () => { let userData; - setNextTime(oneHour * 2); + await setNextTime(oneHour * 2); await distribution.manageUsersInPrivatePool(poolId, [secondAddress, ownerAddress], [wei(1), wei(4)]); expect(await depositToken.balanceOf(secondAddress)).to.eq(wei(1000)); @@ -588,7 +625,7 @@ describe('Distribution', () => { expect(userData.deposited).to.eq(wei(4)); expect(userData.pendingRewards).to.eq(0); - setNextTime(oneHour * 3); + await setNextTime(oneHour * 3); await distribution.manageUsersInPrivatePool(poolId, [secondAddress, ownerAddress], [wei(1), wei(4)]); expect(await depositToken.balanceOf(secondAddress)).to.eq(wei(1000)); @@ -644,7 +681,9 @@ describe('Distribution', () => { it('should stake correctly', async () => { // A stakes 1 token - await distribution.stake(poolId, wei(1)); + const tx = await distribution.stake(poolId, wei(1)); + await expect(tx).to.emit(distribution, 'UserStaked').withArgs(poolId, ownerAddress, wei(1)); + let userData = await distribution.usersData(ownerAddress, poolId); expect(userData.deposited).to.eq(wei(1)); expect(userData.rate).to.eq(0); @@ -714,7 +753,8 @@ describe('Distribution', () => { // Claim after 2 days await setNextTime(oneDay + oneDay * 2); - await distribution.claim(poolId, SECOND, { value: wei(0.5) }); + const tx = await distribution.claim(poolId, SECOND, { value: wei(0.5) }); + await expect(tx).to.emit(distribution, 'UserClaimed').withArgs(poolId, secondAddress, wei(198)); expect(await rewardToken.balanceOf(secondAddress)).to.eq(wei(198)); userData = await distribution.usersData(secondAddress, poolId); @@ -1045,15 +1085,14 @@ describe('Distribution', () => { await setNextTime(oneDay + oneDay); let tx = await distribution.claim(poolId, OWNER, { value: wei(0.5) }); - expect(tx).to.changeTokenBalance(rewardToken, OWNER, wei(1)); - + 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) }); - expect(tx).to.changeTokenBalance(rewardToken, OWNER, wei(0)); + await expect(tx).to.changeTokenBalance(rewardToken, OWNER, wei(98)); userData = await distribution.usersData(OWNER, poolId); expect(userData.pendingRewards).to.equal(wei(0)); }); @@ -1138,7 +1177,7 @@ describe('Distribution', () => { const poolId = 0; beforeEach(async () => { - await distribution.createPool(getDefaultPool()); + await distribution.createPool({ ...getDefaultPool(), withdrawLockPeriodAfterStake: oneDay - 1 }); }); it('should correctly withdraw, few users, withdraw all', async () => { @@ -1152,7 +1191,8 @@ describe('Distribution', () => { // Withdraw after 2 days await setNextTime(oneDay + oneDay * 2); - await distribution.connect(OWNER).withdraw(poolId, wei(999)); + const tx = await distribution.connect(OWNER).withdraw(poolId, wei(999)); + await expect(tx).to.emit(distribution, 'UserWithdrawn').withArgs(poolId, ownerAddress, wei(3)); await distribution.claim(poolId, OWNER, { value: wei(0.5) }); expect(await depositToken.balanceOf(ownerAddress)).to.eq(wei(1000)); @@ -1271,7 +1311,7 @@ describe('Distribution', () => { expect(await depositToken.balanceOf(distribution)).to.eq(wei(20)); await setNextTime(oneDay + oneDay); - await depositToken.setTotalPooledEther(wei(0.8, 25)); + await depositToken.setTotalPooledEther(((await depositToken.totalPooledEther()) * 8n) / 10n); expect(await depositToken.balanceOf(distribution)).to.eq(wei(16)); let tx = await distribution.withdraw(poolId, wei(999)); @@ -1293,7 +1333,7 @@ describe('Distribution', () => { await distribution.withdraw(poolId, wei(10)); - await expect(distribution.withdraw(poolId, 0)).to.be.revertedWith('DS: invalid withdraw amount'); + await expect(distribution.withdraw(poolId, 0)).to.be.revertedWith('DS: nothing to withdraw'); }); it("should revert if user didn't stake", async () => { await expect(distribution.withdraw(poolId, 1)).to.be.revertedWith("DS: user isn't staked"); @@ -1323,6 +1363,13 @@ describe('Distribution', () => { await setNextTime(oneDay); + await expect(distribution.withdraw(poolId, wei(0.1))).to.be.revertedWith('DS: pool withdraw is locked'); + }); + it("should revert if `withdrawLockPeriodAfterStake didn't pass", async () => { + await setNextTime(oneDay * 10); + + await distribution.stake(poolId, wei(1)); + await expect(distribution.withdraw(poolId, wei(0.1))).to.be.revertedWith('DS: pool withdraw is locked'); }); }); @@ -1469,6 +1516,7 @@ describe('Distribution', () => { decreaseInterval: oneDay, withdrawLockPeriod: 1, claimLockPeriod: 1, + withdrawLockPeriodAfterStake: 0, initialReward: wei(14400), rewardDecrease: wei(2.468994701), minimalStake: wei(0.1), @@ -1541,7 +1589,7 @@ describe('Distribution', () => { it('should return overplus if deposited token increased', async () => { await distribution.stake(0, wei(1)); - await depositToken.setTotalPooledEther(wei(2, 25)); + await depositToken.setTotalPooledEther((await depositToken.totalPooledEther()) * 2n); let overplus = await distribution.overplus(); expect(overplus).to.eq(wei(1)); @@ -1551,12 +1599,12 @@ describe('Distribution', () => { overplus = await distribution.overplus(); expect(overplus).to.eq(wei(1)); - await depositToken.setTotalPooledEther(wei(1, 25)); + await depositToken.setTotalPooledEther((await depositToken.totalPooledEther()) / 2n); overplus = await distribution.overplus(); expect(overplus).to.eq(0); - await depositToken.setTotalPooledEther(wei(5, 25)); + await depositToken.setTotalPooledEther((await depositToken.totalPooledEther()) * 5n); overplus = await distribution.overplus(); expect(overplus).to.eq(wei(5.5)); @@ -1577,12 +1625,14 @@ describe('Distribution', () => { await distribution.stake(1, wei(1)); - await depositToken.setTotalPooledEther(wei(2, 25)); + await depositToken.setTotalPooledEther((await depositToken.totalPooledEther()) * 2n); const overplus = await distribution.overplus(); expect(overplus).to.eq(wei(1)); + 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.changeTokenBalance(depositToken, distribution, wei(-1)); expect(await wstETH.balanceOf(l2TokenReceiverAddress)).to.eq(wei(1)); }); @@ -1607,6 +1657,7 @@ const _getRewardTokenFromPool = async (distribution: Distribution, amount: bigin decreaseInterval: 1, withdrawLockPeriod: 0, claimLockPeriod: 0, + withdrawLockPeriodAfterStake: 0, isPublic: true, minimalStake: 0, }; @@ -1636,6 +1687,8 @@ const _comparePoolStructs = (a: IDistribution.PoolStruct, b: IDistribution.PoolS a.payoutStart.toString() === b.payoutStart.toString() && a.decreaseInterval.toString() === b.decreaseInterval.toString() && a.withdrawLockPeriod.toString() === b.withdrawLockPeriod.toString() && + a.claimLockPeriod.toString() === b.claimLockPeriod.toString() && + a.withdrawLockPeriodAfterStake.toString() === b.withdrawLockPeriodAfterStake.toString() && a.initialReward.toString() === b.initialReward.toString() && a.rewardDecrease.toString() === b.rewardDecrease.toString() && a.minimalStake.toString() === b.minimalStake.toString() && diff --git a/test/L1Sender.test.ts b/test/L1Sender.test.ts index 33fbcac..b6d7932 100644 --- a/test/L1Sender.test.ts +++ b/test/L1Sender.test.ts @@ -4,7 +4,9 @@ import { ethers } from 'hardhat'; import { GatewayRouterMock, + IL1Sender, L1Sender, + L1SenderV2, L2MessageReceiver, LZEndpointMock, MOR, @@ -39,49 +41,75 @@ describe('L1Sender', () => { before(async () => { [OWNER, SECOND] = await ethers.getSigners(); - const [LZEndpointMock, Mor, L1Sender, GatewayRouterMock, StETHMock, WStETHMock, L2MessageReceiver] = - await Promise.all([ - ethers.getContractFactory('LZEndpointMock'), - ethers.getContractFactory('MOR'), - ethers.getContractFactory('L1Sender'), - ethers.getContractFactory('GatewayRouterMock'), - ethers.getContractFactory('StETHMock'), - ethers.getContractFactory('WStETHMock'), - ethers.getContractFactory('L2MessageReceiver'), - ]); - - [lZEndpointMockL1, lZEndpointMockL2, rewardToken, l1Sender, unwrappedToken, l2MessageReceiver, gatewayRouter] = - await Promise.all([ - LZEndpointMock.deploy(senderChainId), - LZEndpointMock.deploy(receiverChainId), - Mor.deploy(wei(100)), - L1Sender.deploy(), - StETHMock.deploy(), - L2MessageReceiver.deploy(), - GatewayRouterMock.deploy(), - ]); - + const [ + ERC1967ProxyFactory, + LZEndpointMock, + Mor, + L1Sender, + GatewayRouterMock, + StETHMock, + WStETHMock, + L2MessageReceiver, + ] = await Promise.all([ + ethers.getContractFactory('ERC1967Proxy'), + ethers.getContractFactory('LZEndpointMock'), + ethers.getContractFactory('MOR'), + ethers.getContractFactory('L1Sender'), + ethers.getContractFactory('GatewayRouterMock'), + ethers.getContractFactory('StETHMock'), + ethers.getContractFactory('WStETHMock'), + ethers.getContractFactory('L2MessageReceiver'), + ]); + + let l1SenderImplementation: L1Sender; + let l2MessageReceiverImplementation: L2MessageReceiver; + + [ + lZEndpointMockL1, + lZEndpointMockL2, + rewardToken, + l1SenderImplementation, + unwrappedToken, + l2MessageReceiverImplementation, + gatewayRouter, + ] = await Promise.all([ + LZEndpointMock.deploy(senderChainId), + LZEndpointMock.deploy(receiverChainId), + Mor.deploy(wei(100)), + L1Sender.deploy(), + StETHMock.deploy(), + L2MessageReceiver.deploy(), + GatewayRouterMock.deploy(), + ]); depositToken = await WStETHMock.deploy(unwrappedToken); - await l1Sender.setDepositTokenConfig({ - token: depositToken, - gateway: gatewayRouter, - receiver: SECOND, - }); + const l2MessageReceiverProxy = await ERC1967ProxyFactory.deploy(l2MessageReceiverImplementation, '0x'); + l2MessageReceiver = L2MessageReceiver.attach(l2MessageReceiverProxy) as L2MessageReceiver; + await l2MessageReceiver.L2MessageReceiver__init(); - await l1Sender.setRewardTokenConfig({ + const rewardTokenConfig: IL1Sender.RewardTokenConfigStruct = { gateway: lZEndpointMockL1, receiver: l2MessageReceiver, receiverChainId: receiverChainId, - }); + }; + const depositTokenConfig: IL1Sender.DepositTokenConfigStruct = { + token: depositToken, + gateway: gatewayRouter, + receiver: SECOND, + }; + + const l1SenderProxy = await ERC1967ProxyFactory.deploy(l1SenderImplementation, '0x'); + l1Sender = L1Sender.attach(l1SenderProxy) as L1Sender; + await l1Sender.L1Sender__init(OWNER, rewardTokenConfig, depositTokenConfig); - await lZEndpointMockL1.setDestLzEndpoint(l2MessageReceiver, lZEndpointMockL2); await l2MessageReceiver.setParams(rewardToken, { gateway: lZEndpointMockL2, sender: l1Sender, senderChainId: senderChainId, }); + await lZEndpointMockL1.setDestLzEndpoint(l2MessageReceiver, lZEndpointMockL2); + await rewardToken.transferOwnership(l2MessageReceiver); await reverter.snapshot(); @@ -91,12 +119,89 @@ describe('L1Sender', () => { await reverter.revert(); }); + describe('UUPS proxy functionality', () => { + describe('#Distribution_init', () => { + it('should revert if try to call init function twice', async () => { + const reason = 'Initializable: contract is already initialized'; + + const rewardTokenConfig: IL1Sender.RewardTokenConfigStruct = { + gateway: lZEndpointMockL1, + receiver: l2MessageReceiver, + receiverChainId: receiverChainId, + }; + const depositTokenConfig: IL1Sender.DepositTokenConfigStruct = { + token: depositToken, + gateway: gatewayRouter, + receiver: SECOND, + }; + + await expect(l1Sender.L1Sender__init(OWNER, rewardTokenConfig, depositTokenConfig)).to.be.rejectedWith(reason); + }); + it('should setup config', async () => { + expect(await l1Sender.distribution()).to.be.equal(await OWNER.getAddress()); + + expect(await l1Sender.rewardTokenConfig()).to.be.deep.equal([ + await lZEndpointMockL1.getAddress(), + await l2MessageReceiver.getAddress(), + receiverChainId, + ]); + + expect(await l1Sender.depositTokenConfig()).to.be.deep.equal([ + await depositToken.getAddress(), + await gatewayRouter.getAddress(), + await SECOND.getAddress(), + ]); + + expect(await unwrappedToken.allowance(l1Sender, depositToken)).to.be.equal(ethers.MaxUint256); + expect(await depositToken.allowance(l1Sender, gatewayRouter)).to.be.equal(ethers.MaxUint256); + }); + }); + + describe('#_authorizeUpgrade', () => { + it('should correctly upgrade', async () => { + const l1SenderV2Factory = await ethers.getContractFactory('L1SenderV2'); + const l1SenderV2Implementation = await l1SenderV2Factory.deploy(); + + await l1Sender.upgradeTo(l1SenderV2Implementation); + + const l1SenderV2 = l1SenderV2Factory.attach(l1Sender) as L1SenderV2; + + expect(await l1SenderV2.version()).to.eq(2); + }); + it('should revert if caller is not the owner', async () => { + await expect(l1Sender.connect(SECOND).upgradeTo(ZERO_ADDR)).to.be.revertedWith( + 'Ownable: caller is not the owner', + ); + }); + }); + }); + + describe('setDistribution', () => { + it('should set distribution', async () => { + await l1Sender.setDistribution(SECOND); + expect(await l1Sender.distribution()).to.be.equal(await SECOND.getAddress()); + }); + it('should revert if not called by the owner', async () => { + await expect(l1Sender.connect(SECOND).setDistribution(SECOND)).to.be.revertedWith( + 'Ownable: caller is not the owner', + ); + }); + }); + describe('setRewardTokenConfig', () => { - it('check config', async () => { + it('should set new config', async () => { + const newConfig = { + gateway: l2MessageReceiver, + receiver: lZEndpointMockL1, + receiverChainId: 0, + }; + + await l1Sender.setRewardTokenConfig(newConfig); + expect(await l1Sender.rewardTokenConfig()).to.be.deep.equal([ - await lZEndpointMockL1.getAddress(), await l2MessageReceiver.getAddress(), - receiverChainId, + await lZEndpointMockL1.getAddress(), + 0, ]); }); it('should revert if not called by the owner', async () => { @@ -111,16 +216,6 @@ describe('L1Sender', () => { }); describe('setDepositTokenConfig', () => { - it('check config', async () => { - expect(await l1Sender.depositTokenConfig()).to.be.deep.equal([ - await depositToken.getAddress(), - await gatewayRouter.getAddress(), - await SECOND.getAddress(), - ]); - - expect(await unwrappedToken.allowance(l1Sender, depositToken)).to.be.equal(ethers.MaxUint256); - expect(await depositToken.allowance(l1Sender, gatewayRouter)).to.be.equal(ethers.MaxUint256); - }); it('should reset allowances when token and gateway changed', async () => { const [WStETHMock, GatewayRouterMock, StETHMock] = await Promise.all([ ethers.getContractFactory('WStETHMock'), @@ -206,6 +301,24 @@ describe('L1Sender', () => { 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(), + await SECOND.getAddress(), + ]); + + 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({ @@ -245,7 +358,61 @@ describe('L1Sender', () => { it('should revert if not called by the owner', async () => { await expect( l1Sender.connect(SECOND).sendMintMessage(SECOND, '999', OWNER, { value: ethers.parseEther('0.1') }), - ).to.be.revertedWith('Ownable: caller is not the owner'); + ).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'], + [await SECOND.getAddress(), 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/L2MessageReceiver.test.ts b/test/L2MessageReceiver.test.ts index 5391fa2..08832dc 100644 --- a/test/L2MessageReceiver.test.ts +++ b/test/L2MessageReceiver.test.ts @@ -2,8 +2,8 @@ import { SignerWithAddress } from '@nomicfoundation/hardhat-ethers/signers'; import { expect } from 'chai'; import { ethers } from 'hardhat'; -import { L2MessageReceiver, MOR } from '@/generated-types/ethers'; -import { ZERO_ADDR } from '@/scripts/utils/constants'; +import { L2MessageReceiver, L2MessageReceiverV2, MOR } 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'; @@ -19,13 +19,18 @@ describe('L2MessageReceiver', () => { before(async () => { [OWNER, SECOND, THIRD] = await ethers.getSigners(); - const [L2MessageReceiver, Mor] = await Promise.all([ + const [ERC1967ProxyFactory, L2MessageReceiver, Mor] = await Promise.all([ + ethers.getContractFactory('ERC1967Proxy'), ethers.getContractFactory('L2MessageReceiver'), ethers.getContractFactory('MOR'), - ethers.getContractFactory('LZEndpointMock'), ]); - [mor, l2MessageReceiver] = await Promise.all([Mor.deploy(wei(100)), L2MessageReceiver.deploy()]); + mor = await Mor.deploy(wei(100)); + + const l2MessageReceiverImplementation = await L2MessageReceiver.deploy(); + const l2MessageReceiverProxy = await ERC1967ProxyFactory.deploy(l2MessageReceiverImplementation, '0x'); + l2MessageReceiver = L2MessageReceiver.attach(l2MessageReceiverProxy) as L2MessageReceiver; + await l2MessageReceiver.L2MessageReceiver__init(); await l2MessageReceiver.setParams(mor, { gateway: THIRD, @@ -35,14 +40,42 @@ describe('L2MessageReceiver', () => { await mor.transferOwnership(l2MessageReceiver); - reverter.snapshot(); + await reverter.snapshot(); }); beforeEach(async () => { await reverter.revert(); }); - describe('setParams', () => { + describe('UUPS proxy functionality', () => { + describe('#Distribution_init', () => { + 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); + }); + }); + + describe('#_authorizeUpgrade', () => { + it('should correctly upgrade', async () => { + const l2MessageReceiverV2Factory = await ethers.getContractFactory('L2MessageReceiverV2'); + const l2MessageReceiverV2Implementation = await l2MessageReceiverV2Factory.deploy(); + + await l2MessageReceiver.upgradeTo(l2MessageReceiverV2Implementation); + + const l2MessageReceiverV2 = l2MessageReceiverV2Factory.attach(l2MessageReceiver) as L2MessageReceiverV2; + + expect(await l2MessageReceiverV2.version()).to.eq(2); + }); + it('should revert if caller is not the owner', async () => { + await expect(l2MessageReceiver.connect(SECOND).upgradeTo(ZERO_ADDR)).to.be.revertedWith( + 'Ownable: caller is not the owner', + ); + }); + }); + }); + + describe('#setParams', () => { it('should set params', async () => { await l2MessageReceiver.setParams(mor, { gateway: ZERO_ADDR, @@ -65,7 +98,7 @@ describe('L2MessageReceiver', () => { }); }); - describe('lzReceive', () => { + describe('#lzReceive', () => { it('should update nonce and mint tokens', async () => { const address = ethers.solidityPacked( ['address', 'address'], @@ -75,10 +108,9 @@ describe('L2MessageReceiver', () => { ['address', 'uint256'], [await SECOND.getAddress(), wei(1)], ); - const tx = await l2MessageReceiver.connect(THIRD).lzReceive(2, address, 5, payload); await expect(tx).to.changeTokenBalance(mor, SECOND, wei(1)); - expect(await l2MessageReceiver.nonce()).to.be.equal(5); + expect(await l2MessageReceiver.isNonceUsed(2, 5)).to.be.equal(true); }); it('should update nonce and mint tokens', async () => { const address = ethers.solidityPacked( @@ -89,45 +121,126 @@ describe('L2MessageReceiver', () => { ['address', 'uint256'], [await SECOND.getAddress(), wei(99)], ); - let tx = await l2MessageReceiver.connect(THIRD).lzReceive(2, address, 5, payload); await expect(tx).to.changeTokenBalance(mor, SECOND, wei(99)); - expect(await l2MessageReceiver.nonce()).to.be.equal(5); - + expect(await l2MessageReceiver.isNonceUsed(2, 5)).to.be.equal(true); payload = ethers.AbiCoder.defaultAbiCoder().encode(['address', 'uint256'], [await SECOND.getAddress(), wei(2)]); - tx = await l2MessageReceiver.connect(THIRD).lzReceive(2, address, 6, payload); await expect(tx).to.changeTokenBalance(mor, SECOND, wei(1)); - expect(await l2MessageReceiver.nonce()).to.be.equal(6); - + expect(await l2MessageReceiver.isNonceUsed(2, 6)).to.be.equal(true); payload = ethers.AbiCoder.defaultAbiCoder().encode(['address', 'uint256'], [await SECOND.getAddress(), wei(2)]); - tx = await l2MessageReceiver.connect(THIRD).lzReceive(2, address, 7, payload); await expect(tx).to.changeTokenBalance(mor, SECOND, wei(0)); - expect(await l2MessageReceiver.nonce()).to.be.equal(7); - + expect(await l2MessageReceiver.isNonceUsed(2, 7)).to.be.equal(true); payload = ethers.AbiCoder.defaultAbiCoder().encode(['address', 'uint256'], [await SECOND.getAddress(), wei(0)]); - tx = await l2MessageReceiver.connect(THIRD).lzReceive(2, address, 8, payload); await expect(tx).to.changeTokenBalance(mor, SECOND, wei(0)); - expect(await l2MessageReceiver.nonce()).to.be.equal(8); - }); - it('should revert if provided wrong nonce', async () => { - await expect(l2MessageReceiver.lzReceive(1, '0x', 0, '0x')).to.be.revertedWith('L2MR: invalid nonce'); + expect(await l2MessageReceiver.isNonceUsed(2, 8)).to.be.equal(true); }); it('should revert if provided wrong lzEndpoint', async () => { await expect(l2MessageReceiver.lzReceive(0, '0x', 1, '0x')).to.be.revertedWith('L2MR: invalid gateway'); }); - it('should revert if provided wrong chainId', async () => { - await expect(l2MessageReceiver.connect(THIRD).lzReceive(0, '0x', 1, '0x')).to.be.revertedWith( - 'L2MR: invalid sender chain ID', + it('should fail if provided wrong nonce', async () => { + const address = ethers.solidityPacked( + ['address', 'address'], + [await OWNER.getAddress(), await l2MessageReceiver.getAddress()], + ); + const payload = ethers.AbiCoder.defaultAbiCoder().encode( + ['address', 'uint256'], + [await SECOND.getAddress(), wei(99)], ); + 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)); + }); + }); + + describe('#nonblockingLzReceive', () => { + it('should revert if invalid caller', async () => { + await expect(l2MessageReceiver.nonblockingLzReceive(2, '0x', 999, '0x')).to.be.revertedWith( + 'L2MR: invalid caller', + ); + }); + }); + + describe('#retryMessage', () => { + let senderAndReceiverAddresses = ''; + let payload = ''; + const chainId = 2; + + beforeEach(async () => { + senderAndReceiverAddresses = ethers.solidityPacked( + ['address', 'address'], + [await SECOND.getAddress(), await l2MessageReceiver.getAddress()], + ); + payload = ethers.AbiCoder.defaultAbiCoder().encode(['address', 'uint256'], [await SECOND.getAddress(), 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, { + gateway: THIRD, + sender: SECOND, + senderChainId: 2, + }); + + const tx = await l2MessageReceiver.retryMessage(chainId, senderAndReceiverAddresses, 999, payload); + await expect(tx).to.changeTokenBalance(mor, SECOND, wei(99)); + + expect(await l2MessageReceiver.failedMessages(chainId, senderAndReceiverAddresses, 999)).to.eq(ZERO_BYTES32); + }); + it('should revert if invalid caller', async () => { + await expect(l2MessageReceiver.nonblockingLzReceive(chainId, '0x', 999, '0x')).to.be.revertedWith( + 'L2MR: invalid caller', + ); + }); + it('should revert if provided wrong chainId', async () => { + await l2MessageReceiver.setParams(mor, { + gateway: THIRD, + sender: SECOND, + senderChainId: 3, + }); + + await expect( + l2MessageReceiver.retryMessage(chainId, senderAndReceiverAddresses, 999, payload), + ).to.be.revertedWith('L2MR: invalid sender chain ID'); }); it('should revert if provided wrong sender', async () => { - await expect(l2MessageReceiver.connect(THIRD).lzReceive(2, '0x', 1, '0x')).to.be.revertedWith( - 'L2MR: invalid sender address', + await expect( + l2MessageReceiver.retryMessage(chainId, senderAndReceiverAddresses, 999, payload), + ).to.be.revertedWith('L2MR: invalid sender address'); + }); + it('should revert if provided wrong message', async () => { + await expect( + l2MessageReceiver.retryMessage(chainId, senderAndReceiverAddresses, 998, payload), + ).to.be.revertedWith('L2MR: no stored message'); + }); + it('should revert if provided wrong payload', async () => { + 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, { + gateway: THIRD, + sender: SECOND, + senderChainId: 2, + }); + + await l2MessageReceiver.retryMessage(chainId, senderAndReceiverAddresses, 999, payload); + + await expect( + l2MessageReceiver.retryMessage(chainId, senderAndReceiverAddresses, 999, payload), + ).to.be.revertedWith('L2MR: no stored message'); + }); }); }); diff --git a/test/L2TokenReceiver.test.ts b/test/L2TokenReceiver.test.ts index 8cf3b00..aa49583 100644 --- a/test/L2TokenReceiver.test.ts +++ b/test/L2TokenReceiver.test.ts @@ -8,6 +8,7 @@ import { Reverter } from './helpers/reverter'; import { IL2TokenReceiver, L2TokenReceiver, + L2TokenReceiverV2, MOR, NonfungiblePositionManagerMock, StETHMock, @@ -19,6 +20,7 @@ import { wei } from '@/scripts/utils/utils'; describe('L2TokenReceiver', () => { const reverter = new Reverter(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars let OWNER: SignerWithAddress; let SECOND: SignerWithAddress; @@ -31,59 +33,101 @@ describe('L2TokenReceiver', () => { before(async () => { [OWNER, SECOND] = await ethers.getSigners(); - const [L2TokenReceiver, StETHMock, Mor, SwapRouterMock, NonfungiblePositionManagerMock] = await Promise.all([ - ethers.getContractFactory('L2TokenReceiver', OWNER), - ethers.getContractFactory('StETHMock'), - ethers.getContractFactory('MOR'), - ethers.getContractFactory('SwapRouterMock'), - ethers.getContractFactory('NonfungiblePositionManagerMock'), - ]); - - [inputToken, outputToken, swapRouter, nonfungiblePositionManager] = await Promise.all([ - StETHMock.deploy(), - Mor.deploy(wei(100)), - SwapRouterMock.deploy(), - NonfungiblePositionManagerMock.deploy(), - ]); - - l2TokenReceiver = await L2TokenReceiver.deploy(swapRouter, nonfungiblePositionManager, { + 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'), + ]); + + let l2TokenReceiverImplementation: L2TokenReceiver; + + [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 L2TokenReceiver; + await l2TokenReceiver.L2TokenReceiver__init(swapRouter, nonfungiblePositionManager, { tokenIn: inputToken, tokenOut: outputToken, fee: 500, sqrtPriceLimitX96: 0, }); - reverter.snapshot(); + await reverter.snapshot(); }); beforeEach(async () => { await reverter.revert(); }); - describe('constructor', () => { - it('should set router', async () => { - expect(await l2TokenReceiver.router()).to.equal(await swapRouter.getAddress()); + + describe('UUPS proxy functionality', () => { + describe('#Distribution_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.params(); + + 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); + }); }); - it('should set params', async () => { - const defaultParams = getDefaultSwapParams(await inputToken.getAddress(), await outputToken.getAddress()); - const params = await l2TokenReceiver.params(); + describe('#_authorizeUpgrade', () => { + it('should correctly upgrade', async () => { + const l2TokenReceiverV2Factory = await ethers.getContractFactory('L2TokenReceiverV2'); + const l2TokenReceiverV2Implementation = await l2TokenReceiverV2Factory.deploy(); - 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); - }); + await l2TokenReceiver.upgradeTo(l2TokenReceiverV2Implementation); - 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); + const l2TokenReceiverV2 = l2TokenReceiverV2Factory.attach(l2TokenReceiver) as L2TokenReceiverV2; + + expect(await l2TokenReceiverV2.version()).to.eq(2); + }); + 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('0x79f94a20')).to.be.true; + expect(await l2TokenReceiver.supportsInterface('0x42861043')).to.be.true; }); it('should support IERC165', async () => { expect(await l2TokenReceiver.supportsInterface('0x01ffc9a7')).to.be.true; @@ -152,7 +196,7 @@ describe('L2TokenReceiver', () => { describe('#increaseLiquidityCurrentRange', () => { it('should return if caller is not the owner', async () => { - await expect(l2TokenReceiver.connect(SECOND).increaseLiquidityCurrentRange(1, 1, 1)).to.be.revertedWith( + await expect(l2TokenReceiver.connect(SECOND).increaseLiquidityCurrentRange(1, 1, 1, 0, 0)).to.be.revertedWith( 'Ownable: caller is not the owner', ); }); diff --git a/test/MOR.test.ts b/test/MOR.test.ts index 8393b99..8332709 100644 --- a/test/MOR.test.ts +++ b/test/MOR.test.ts @@ -21,7 +21,7 @@ describe('MOR', () => { const MORFactory = await ethers.getContractFactory('MOR'); mor = await MORFactory.deploy(cap); - reverter.snapshot(); + await reverter.snapshot(); }); afterEach(async () => { @@ -41,7 +41,7 @@ describe('MOR', () => { describe('supportsInterface', () => { it('should support IMOR', async () => { - expect(await mor.supportsInterface('0x3705179b')).to.be.true; + expect(await mor.supportsInterface('0x75937bf3')).to.be.true; }); it('should support IERC20', async () => { expect(await mor.supportsInterface('0x36372b07')).to.be.true; diff --git a/test/fork/L1Sender.fork.test.ts b/test/fork/L1Sender.fork.test.ts index 947d064..be7467f 100644 --- a/test/fork/L1Sender.fork.test.ts +++ b/test/fork/L1Sender.fork.test.ts @@ -7,6 +7,7 @@ import { Reverter } from '../helpers/reverter'; import { IGatewayRouter, IGatewayRouter__factory, + IL1Sender, IStETH, IStETH__factory, IWStETH, @@ -21,7 +22,7 @@ describe('L1Sender Fork', () => { let OWNER: SignerWithAddress; let SECOND: SignerWithAddress; - const arbitrumBridgeGatewayRouterAddress = '0x0F25c1DC2a9922304f2eac71DCa9B07E310e8E5a'; + const arbitrumBridgeGatewayRouterAddress = '0x72Ce9c846789fdB6fC1f34aC4AD25Dd9ef7031ef'; const lzEndpointAddress = '0x66A71Dcef29A0fFBDBE3c6a460a3B5BC225Cd675'; const stethAddress = '0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84'; const wstethAddress = '0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0'; @@ -49,20 +50,26 @@ describe('L1Sender Fork', () => { wsteth = IWStETH__factory.connect(wstethAddress, OWNER); steth = IStETH__factory.connect(stethAddress, OWNER); - const L1Sender = await ethers.getContractFactory('L1Sender', OWNER); - l1Sender = await L1Sender.deploy(); + const [ERC1967ProxyFactory, L1Sender] = await Promise.all([ + ethers.getContractFactory('ERC1967Proxy', OWNER), + ethers.getContractFactory('L1Sender', OWNER), + ]); - await l1Sender.setDepositTokenConfig({ + const rewardTokenConfig: IL1Sender.RewardTokenConfigStruct = { + gateway: lzEndpointAddress, + receiver: SECOND, + receiverChainId: 110, + }; + const depositTokenConfig: IL1Sender.DepositTokenConfigStruct = { token: wsteth, gateway: arbitrumBridgeGatewayRouter, receiver: SECOND, - }); + }; - await l1Sender.setRewardTokenConfig({ - gateway: lzEndpointAddress, - receiver: SECOND, - receiverChainId: 110, - }); + const l1SenderImplementation = await L1Sender.deploy(); + const l1SenderProxy = await ERC1967ProxyFactory.deploy(l1SenderImplementation, '0x'); + l1Sender = L1Sender.attach(l1SenderProxy) as L1Sender; + await l1Sender.L1Sender__init(OWNER, rewardTokenConfig, depositTokenConfig); await reverter.snapshot(); }); diff --git a/test/fork/L2TokenReceiver.fork.test.ts b/test/fork/L2TokenReceiver.fork.test.ts index 4a6c5a3..bbc2d98 100644 --- a/test/fork/L2TokenReceiver.fork.test.ts +++ b/test/fork/L2TokenReceiver.fork.test.ts @@ -30,7 +30,6 @@ describe('L2TokenReceiver Fork', () => { const wstethAddress = '0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0'; const usdcAddress = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'; - const wstethToUsdcRatio = wei(2399.01); const richAddress = '0x176F3DAb24a159341c0509bB36B833E7fdd0a132'; @@ -57,8 +56,15 @@ describe('L2TokenReceiver Fork', () => { inputToken = WStETHMock__factory.connect(wstethAddress, OWNER); outputToken = IERC20__factory.connect(usdcAddress, OWNER); - const L2TokenReceiver = await ethers.getContractFactory('L2TokenReceiver', OWNER); - l2TokenReceiver = await L2TokenReceiver.deploy( + const [ERC1967ProxyFactory, L2TokenReceiver] = await Promise.all([ + ethers.getContractFactory('ERC1967Proxy', OWNER), + ethers.getContractFactory('L2TokenReceiver', OWNER), + ]); + + const l2TokenReceiverImplementation = await L2TokenReceiver.deploy(); + const l2TokenReceiverProxy = await ERC1967ProxyFactory.deploy(l2TokenReceiverImplementation, '0x'); + l2TokenReceiver = L2TokenReceiver.attach(l2TokenReceiverProxy) as L2TokenReceiver; + await l2TokenReceiver.L2TokenReceiver__init( swapRouter, nonfungiblePositionManager, getDefaultSwapParams(await inputToken.getAddress(), await outputToken.getAddress()), @@ -82,10 +88,11 @@ describe('L2TokenReceiver Fork', () => { }); it('should swap tokens', async () => { + const txResult = await l2TokenReceiver.swap.staticCall(amount, wei(0)); const tx = await l2TokenReceiver.swap(amount, wei(0)); - expect(tx).to.changeTokenBalance(outputToken, OWNER, amount); - expect(tx).to.changeTokenBalance(inputToken, OWNER, -amount * wstethToUsdcRatio); + await expect(tx).to.changeTokenBalance(outputToken, l2TokenReceiver, txResult); + await expect(tx).to.changeTokenBalance(inputToken, l2TokenReceiver, -amount); }); }); @@ -101,16 +108,18 @@ describe('L2TokenReceiver Fork', () => { }); it('should increase liquidity', async () => { - const tx = await l2TokenReceiver.increaseLiquidityCurrentRange.staticCall( + const txResult = await l2TokenReceiver.increaseLiquidityCurrentRange.staticCall( poolId, amountInputToken, amountOutputToken, + 0, + 0, ); - await l2TokenReceiver.increaseLiquidityCurrentRange(poolId, amountInputToken, amountOutputToken); + const tx = await l2TokenReceiver.increaseLiquidityCurrentRange(poolId, amountInputToken, amountOutputToken, 0, 0); - expect(tx).to.changeTokenBalance(outputToken, OWNER, -tx[1]); - expect(tx).to.changeTokenBalance(inputToken, OWNER, -tx[2]); + await expect(tx).to.changeTokenBalance(outputToken, l2TokenReceiver, -txResult[2]); + await expect(tx).to.changeTokenBalance(inputToken, l2TokenReceiver, -txResult[1]); }); it('should set the amount correctly besides the tokens order', async () => { const newParams: IL2TokenReceiver.SwapParamsStruct = { @@ -122,15 +131,17 @@ describe('L2TokenReceiver Fork', () => { await l2TokenReceiver.editParams(newParams); - const tx = await l2TokenReceiver.increaseLiquidityCurrentRange.staticCall( + const txResult = await l2TokenReceiver.increaseLiquidityCurrentRange.staticCall( poolId, amountInputToken, amountOutputToken, + 0, + 0, ); - await l2TokenReceiver.increaseLiquidityCurrentRange(poolId, amountInputToken, amountOutputToken); + const tx = await l2TokenReceiver.increaseLiquidityCurrentRange(poolId, amountInputToken, amountOutputToken, 0, 0); - expect(tx).to.changeTokenBalance(inputToken, OWNER, -tx[1]); - expect(tx).to.changeTokenBalance(outputToken, OWNER, -tx[2]); + await expect(tx).to.changeTokenBalance(inputToken, l2TokenReceiver, -txResult[1]); + await expect(tx).to.changeTokenBalance(outputToken, l2TokenReceiver, -txResult[2]); }); }); }); diff --git a/test/helpers/distribution-helper.ts b/test/helpers/distribution-helper.ts index 3c046b3..aa997b4 100644 --- a/test/helpers/distribution-helper.ts +++ b/test/helpers/distribution-helper.ts @@ -10,6 +10,7 @@ export const getDefaultPool = (): IDistribution.PoolStruct => { decreaseInterval: oneDay, withdrawLockPeriod: 12 * oneHour, claimLockPeriod: 12 * oneHour, + withdrawLockPeriodAfterStake: oneDay, initialReward: wei(100), rewardDecrease: wei(2), minimalStake: wei(0.1), diff --git a/test/helpers/reverter.ts b/test/helpers/reverter.ts index 207c36b..c2ff742 100644 --- a/test/helpers/reverter.ts +++ b/test/helpers/reverter.ts @@ -1,6 +1,7 @@ import { network } from 'hardhat'; export class Reverter { + // eslint-disable-next-line @typescript-eslint/no-explicit-any private snapshotId: any; revert = async () => {