diff --git a/contracts/fee/FeeDistributor.sol b/contracts/fee/FeeDistributor.sol index a81033422..ffb0ba837 100644 --- a/contracts/fee/FeeDistributor.sol +++ b/contracts/fee/FeeDistributor.sol @@ -68,6 +68,21 @@ contract FeeDistributor is ReentrancyGuard, RoleModule { wnt = _wnt; } + // @dev withdraw the specified 'amount' of native token from this contract to 'receiver' + // @param receiver the receiver of the native token + // @param amount the amount of native token to withdraw + function withdrawNativeToken(address receiver, uint256 amount) external onlyTimelockAdmin { + FeeDistributorUtils.withdrawNativeToken(dataStore, receiver, amount); + } + + // @dev withdraw the specified 'amount' of `token` from this contract to `receiver` + // @param token the token to withdraw + // @param amount the amount to withdraw + // @param receiver the address to withdraw to + function withdrawToken(address token, address receiver, uint256 amount) external onlyTimelockAdmin { + FeeDistributorUtils.withdrawToken(dataStore, token, receiver, amount); + } + // @dev initiate the weekly fee distribution process // // The fee distribution process relies on the premise that this function is executed synchronously @@ -205,6 +220,8 @@ contract FeeDistributor is ReentrancyGuard, RoleModule { uint256 requiredGmxAmount = Precision.mulDiv(totalFeeAmountGmx, stakedGmxCurrentChain, totalStakedGmx); uint256 totalGmxBridgedOut; + EventUtils.EventLogData memory eventData; + eventData.uintItems.initItems(3); // validate that the this chain has sufficient GMX to distribute fees if (feeAmountGmxCurrentChain >= requiredGmxAmount) { // only attempt to bridge to other chains if this chain has a surplus of GMX @@ -230,15 +247,17 @@ contract FeeDistributor is ReentrancyGuard, RoleModule { } _setUint(Keys2.feeDistributorFeeAmountGmxKey(block.chainid), newFeeAmountGmxCurrentChain); } - _setDistributionState(uint256(DistributionState.BridgingCompleted)); + uint256 distributionState = uint256(DistributionState.BridgingCompleted); + _setDistributionState(distributionState); + _setUintItem(eventData, 0, "distributionState", distributionState); } else { - _setDistributionState(uint256(DistributionState.ReadDataReceived)); + uint256 distributionState = uint256(DistributionState.ReadDataReceived); + _setDistributionState(distributionState); + _setUintItem(eventData, 0, "distributionState", distributionState); } - EventUtils.EventLogData memory eventData; - eventData.uintItems.initItems(2); - _setUintItem(eventData, 0, "feeAmountGmxCurrentChain", feeAmountGmxCurrentChain); - _setUintItem(eventData, 1, "totalGmxBridgedOut", totalGmxBridgedOut); + _setUintItem(eventData, 1, "feeAmountGmxCurrentChain", feeAmountGmxCurrentChain); + _setUintItem(eventData, 2, "totalGmxBridgedOut", totalGmxBridgedOut); eventData.bytesItems.initItems(1); eventData.bytesItems.setItem(0, "receivedData", abi.encode(receivedData)); _emitFeeDistributionEvent(eventData, "FeeDistributionDataReceived"); @@ -341,8 +360,8 @@ contract FeeDistributor is ReentrancyGuard, RoleModule { _emitFeeDistributionEvent(eventData, "FeeDistributionCompleted"); } - // @dev deposit the calculated referral rewards in the ClaimVault for the specified accounts - // @param token the token in which the referral rewards will be sent + // @dev deposit the calculated referral rewards into the ClaimVault for the specified accounts + // @param token the token in which the referral rewards will be deposited // @param distributionId the distribution id // @param params array of referral rewards deposit parameters function depositReferralRewards( @@ -350,7 +369,7 @@ contract FeeDistributor is ReentrancyGuard, RoleModule { uint256 distributionId, ClaimUtils.DepositParam[] calldata params ) external nonReentrant onlyFeeDistributionKeeper { - // validate the distribution state and that the accounts and amounts arrays are valid lengths + // validate the distribution state _validateDistributionState(DistributionState.None); uint256 tokensForReferralRewards = _getUint(Keys2.feeDistributorReferralRewardsAmountKey(token)); @@ -362,10 +381,10 @@ contract FeeDistributor is ReentrancyGuard, RoleModule { revert Errors.MaxEsGmxReferralRewardsAmountExceeded(tokensForReferralRewards, maxEsGmxReferralRewards); } - uint256 vaultEsGmxBalance = _getFeeDistributorVaultBalance(esGmx); + uint256 vaultEsGmxBalance = _getFeeDistributorVaultBalance(token); uint256 esGmxToBeDeposited = tokensForReferralRewards - cumulativeDepositAmount; if (esGmxToBeDeposited > vaultEsGmxBalance) { - IMintable(esGmx).mint(address(feeDistributorVault), esGmxToBeDeposited - vaultEsGmxBalance); + IMintable(token).mint(address(feeDistributorVault), esGmxToBeDeposited - vaultEsGmxBalance); } // update esGMX bonus reward amounts for each account in the vester contract diff --git a/contracts/fee/FeeDistributorUtils.sol b/contracts/fee/FeeDistributorUtils.sol index f0a959425..0091aa162 100644 --- a/contracts/fee/FeeDistributorUtils.sol +++ b/contracts/fee/FeeDistributorUtils.sol @@ -5,6 +5,7 @@ pragma solidity ^0.8.0; import "../data/DataStore.sol"; import "../data/Keys2.sol"; import "../error/Errors.sol"; +import "../token/TokenUtils.sol"; enum DistributionState { None, @@ -21,6 +22,14 @@ struct Transfer { // @title FeeDistributorUtils library FeeDistributorUtils { + function withdrawNativeToken(DataStore dataStore, address receiver, uint256 amount) external { + TokenUtils.sendNativeToken(dataStore, receiver, amount); + } + + function withdrawToken(DataStore dataStore, address token, address receiver, uint256 amount) external { + TokenUtils.transfer(dataStore, token, receiver, amount); + } + function calculateKeeperCosts(DataStore dataStore) external view returns (uint256, uint256) { address[] memory keepers = dataStore.getAddressArray(Keys2.FEE_DISTRIBUTOR_KEEPER_COSTS); uint256[] memory keepersTargetBalance = dataStore.getUintArray(Keys2.FEE_DISTRIBUTOR_KEEPER_COSTS); @@ -46,7 +55,7 @@ library FeeDistributorUtils { return (keeperCostsV1, keeperCostsV2); } - + function retrieveChainIds(DataStore dataStore) external view returns (uint256[] memory) { uint256[] memory chainIds = dataStore.getUintArray(Keys2.FEE_DISTRIBUTOR_CHAIN_ID); sort(chainIds, 0, int256(chainIds.length - 1)); diff --git a/contracts/fee/FeeDistributorVault.sol b/contracts/fee/FeeDistributorVault.sol index a32e58925..b7c0b02f8 100644 --- a/contracts/fee/FeeDistributorVault.sol +++ b/contracts/fee/FeeDistributorVault.sol @@ -6,4 +6,19 @@ import "../bank/Bank.sol"; contract FeeDistributorVault is Bank { constructor(RoleStore _roleStore, DataStore _dataStore) Bank(_roleStore, _dataStore) {} + + // @dev withdraw the specified 'amount' of native token from this contract to 'receiver' + // @param receiver the receiver of the native token + // @param amount the amount of native token to withdraw + function withdrawNativeToken(address receiver, uint256 amount) external onlyTimelockAdmin { + TokenUtils.sendNativeToken(dataStore, receiver, amount); + } + + // @dev withdraw the specified 'amount' of `token` from this contract to `receiver` + // @param token the token to withdraw + // @param amount the amount to withdraw + // @param receiver the address to withdraw to + function withdrawToken(address token, address receiver, uint256 amount) external onlyTimelockAdmin { + _transferOut(token, receiver, amount); + } } diff --git a/contracts/mock/MintableToken.sol b/contracts/mock/MintableToken.sol index 2647ecc26..23f45db5b 100644 --- a/contracts/mock/MintableToken.sol +++ b/contracts/mock/MintableToken.sol @@ -20,16 +20,14 @@ contract MintableToken is ERC20Permit { // @dev mint tokens to an account // @param account the account to mint to // @param amount the amount of tokens to mint - function mint(address account, uint256 amount) external returns (bool) { + function mint(address account, uint256 amount) external { _mint(account, amount); - return true; } // @dev burn tokens from an account // @param account the account to burn tokens for // @param amount the amount of tokens to burn - function burn(address account, uint256 amount) external returns (bool) { + function burn(address account, uint256 amount) external { _burn(account, amount); - return true; } } diff --git a/contracts/mock/MockFeeDistributor.sol b/contracts/mock/MockFeeDistributor.sol index fb6ebc8c2..87c59641b 100644 --- a/contracts/mock/MockFeeDistributor.sol +++ b/contracts/mock/MockFeeDistributor.sol @@ -81,6 +81,21 @@ contract MockFeeDistributor is ReentrancyGuard, RoleModule { gmxForOracle = _mockVariables.gmxForOracle; } + // @dev withdraw the specified 'amount' of native token from this contract to 'receiver' + // @param receiver the receiver of the native token + // @param amount the amount of native token to withdraw + function withdrawNativeToken(address receiver, uint256 amount) external onlyTimelockAdmin { + FeeDistributorUtils.withdrawNativeToken(dataStore, receiver, amount); + } + + // @dev withdraw the specified 'amount' of `token` from this contract to `receiver` + // @param token the token to withdraw + // @param amount the amount to withdraw + // @param receiver the address to withdraw to + function withdrawToken(address token, address receiver, uint256 amount) external onlyTimelockAdmin { + FeeDistributorUtils.withdrawToken(dataStore, token, receiver, amount); + } + // @dev initiate the weekly fee distribution process // // The fee distribution process relies on the premise that this function is executed synchronously @@ -218,6 +233,8 @@ contract MockFeeDistributor is ReentrancyGuard, RoleModule { uint256 requiredGmxAmount = Precision.mulDiv(totalFeeAmountGmx, stakedGmxCurrentChain, totalStakedGmx); uint256 totalGmxBridgedOut; + EventUtils.EventLogData memory eventData; + eventData.uintItems.initItems(3); // validate that the this chain has sufficient GMX to distribute fees if (feeAmountGmxCurrentChain >= requiredGmxAmount) { // only attempt to bridge to other chains if this chain has a surplus of GMX @@ -243,15 +260,17 @@ contract MockFeeDistributor is ReentrancyGuard, RoleModule { } _setUint(Keys2.feeDistributorFeeAmountGmxKey(mockChainId), newFeeAmountGmxCurrentChain); } - _setDistributionState(uint256(DistributionState.BridgingCompleted)); + uint256 distributionState = uint256(DistributionState.BridgingCompleted); + _setDistributionState(distributionState); + _setUintItem(eventData, 0, "distributionState", distributionState); } else { - _setDistributionState(uint256(DistributionState.ReadDataReceived)); + uint256 distributionState = uint256(DistributionState.ReadDataReceived); + _setDistributionState(distributionState); + _setUintItem(eventData, 0, "distributionState", distributionState); } - EventUtils.EventLogData memory eventData; - eventData.uintItems.initItems(2); - _setUintItem(eventData, 0, "feeAmountGmxCurrentChain", feeAmountGmxCurrentChain); - _setUintItem(eventData, 1, "totalGmxBridgedOut", totalGmxBridgedOut); + _setUintItem(eventData, 1, "feeAmountGmxCurrentChain", feeAmountGmxCurrentChain); + _setUintItem(eventData, 2, "totalGmxBridgedOut", totalGmxBridgedOut); eventData.bytesItems.initItems(1); eventData.bytesItems.setItem(0, "receivedData", abi.encode(receivedData)); _emitFeeDistributionEvent(eventData, "FeeDistributionDataReceived"); @@ -354,8 +373,8 @@ contract MockFeeDistributor is ReentrancyGuard, RoleModule { _emitFeeDistributionEvent(eventData, "FeeDistributionCompleted"); } - // @dev deposit the calculated referral rewards in the ClaimVault for the specified accounts - // @param token the token in which the referral rewards will be sent + // @dev deposit the calculated referral rewards into the ClaimVault for the specified accounts + // @param token the token in which the referral rewards will be deposited // @param distributionId the distribution id // @param params array of referral rewards deposit parameters function depositReferralRewards( @@ -363,7 +382,7 @@ contract MockFeeDistributor is ReentrancyGuard, RoleModule { uint256 distributionId, ClaimUtils.DepositParam[] calldata params ) external nonReentrant onlyFeeDistributionKeeper { - // validate the distribution state and that the accounts and amounts arrays are valid lengths + // validate the distribution state _validateDistributionState(DistributionState.None); uint256 tokensForReferralRewards = _getUint(Keys2.feeDistributorReferralRewardsAmountKey(token)); @@ -375,10 +394,10 @@ contract MockFeeDistributor is ReentrancyGuard, RoleModule { revert Errors.MaxEsGmxReferralRewardsAmountExceeded(tokensForReferralRewards, maxEsGmxReferralRewards); } - uint256 vaultEsGmxBalance = _getFeeDistributorVaultBalance(esGmx); + uint256 vaultEsGmxBalance = _getFeeDistributorVaultBalance(token); uint256 esGmxToBeDeposited = tokensForReferralRewards - cumulativeDepositAmount; if (esGmxToBeDeposited > vaultEsGmxBalance) { - IMintable(esGmx).mint(address(feeDistributorVault), esGmxToBeDeposited - vaultEsGmxBalance); + IMintable(token).mint(address(feeDistributorVault), esGmxToBeDeposited - vaultEsGmxBalance); } // update esGMX bonus reward amounts for each account in the vester contract diff --git a/contracts/mock/MockGMX_LockboxAdapter.sol b/contracts/mock/MockGMX_LockboxAdapter.sol new file mode 100644 index 000000000..092c2f77a --- /dev/null +++ b/contracts/mock/MockGMX_LockboxAdapter.sol @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.22; + +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; + +import { Origin } from "@layerzerolabs/oapp-evm/contracts/oapp/OApp.sol"; +import { OFTMsgCodec } from "@layerzerolabs/oft-evm/contracts/libs/OFTMsgCodec.sol"; + +import { OFTAdapter } from "@layerzerolabs/oft-evm/contracts/OFTAdapter.sol"; + +import { MockOverridableInboundRateLimiter } from "./MockOverridableInboundRateLimiter.sol"; + +/** + * @title GMX_LockboxAdapter Contract + * @author LayerZero Labs (@shankars99) + * @dev Implementation of a lockbox-style OFT adapter with overridable rate limiting. + * @dev This contract adapts an existing ERC-20 token to OFT functionality using lock/unlock mechanism. + * @dev Unlike MintBurnOFTAdapter, this locks tokens in the contract rather than minting/burning. + * @dev This contract is meant to be used on Arbitrum. + */ +contract MockGMX_LockboxAdapter is OFTAdapter, MockOverridableInboundRateLimiter { + using OFTMsgCodec for bytes; + using OFTMsgCodec for bytes32; + + constructor( + RateLimitConfig[] memory _rateLimitConfigs, + address _token, + address _lzEndpoint, + address _delegate + ) OFTAdapter(_token, _lzEndpoint, _delegate) Ownable() { + _setRateLimits(_rateLimitConfigs); + } + + /** + * @notice Override the base _debit() function to apply rate limiting before super._debit() + * @dev This function is called when a debit is made from the OFT. + * @param _from The address from which the debit is made. + * @param _amountLD The amount to debit in local denomination. + * @param _minAmountLD The minimum amount to debit in local denomination. + * @param _dstEid The destination endpoint ID. + */ + function _debit( + address _from, + uint256 _amountLD, + uint256 _minAmountLD, + uint32 _dstEid + ) internal virtual override returns (uint256 amountSentLD, uint256 amountReceivedLD) { + /// @dev amountSentLD is amountLD with dust removed + /// @dev amountReceivedLD is amountSentLD with other token amount changes such as fee, etc. + /// @dev For lockbox adapters, these are typically the same (no fees) + (amountSentLD, amountReceivedLD) = super._debit(_from, _amountLD, _minAmountLD, _dstEid); + + _outflowOverridable(_from, amountSentLD, _dstEid); + } + + /** + * @notice Override the base _lzReceive() function to apply rate limiting before super._lzReceive() + * @dev This function is called when a message is received from another chain. + * @param _origin The origin of the message. + * @param _guid The GUID of the message. + * @param _message The message data. + * @param _executor The address of the executor. + * @param _extraData Additional data for the message. + */ + function _lzReceive( + Origin calldata _origin, + bytes32 _guid, + bytes calldata _message, + address _executor, // @dev unused in the default implementation. + bytes calldata _extraData // @dev unused in the default implementation. + ) internal virtual override { + address toAddress = _message.sendTo().bytes32ToAddress(); + + /// @dev We can assume that every layerzero message is an OFT transfer and that there are no non-token messages + _inflowOverridable(_guid, toAddress, _toLD(_message.amountSD()), _origin.srcEid); + + super._lzReceive(_origin, _guid, _message, _executor, _extraData); + } +} diff --git a/contracts/mock/MockGMX_MintBurnAdapter.sol b/contracts/mock/MockGMX_MintBurnAdapter.sol new file mode 100644 index 000000000..3e26f4484 --- /dev/null +++ b/contracts/mock/MockGMX_MintBurnAdapter.sol @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.22; + +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; + +import { Origin } from "@layerzerolabs/oapp-evm/contracts/oapp/OApp.sol"; +import { OFTMsgCodec } from "@layerzerolabs/oft-evm/contracts/libs/OFTMsgCodec.sol"; + +import { MintBurnOFTAdapter, IMintableBurnable } from "@layerzerolabs/oft-evm/contracts/MintBurnOFTAdapter.sol"; + +import { MockOverridableInboundRateLimiter } from "./MockOverridableInboundRateLimiter.sol"; + +/** + * @title IGMXMinterBurnable + * @dev Interface for the GMX token contract with minting and burning functionality + */ +interface IGMXMinterBurnable { + /** + * @notice Burns tokens from an account + * @param _account The account to burn tokens from + * @param _amount The amount of tokens to burn + * @dev Can only be called by authorized minters + */ + function burn(address _account, uint256 _amount) external; + + /** + * @notice Mints tokens to an account + * @param _account The account to mint tokens to + * @param _amount The amount of tokens to mint + * @dev Can only be called by authorized minters + */ + function mint(address _account, uint256 _amount) external; +} + +/** + * @title GMX_MintBurnAdapter Contract + * @author LayerZero Labs (@shankars99) + * @dev GMX_MintBurnAdapter is a contract that adapts an ERC-20 token with external mint and burn logic to the OFT functionality. + * @dev For existing ERC20 tokens with exposed mint and burn permissions, this can be used to convert the token to crosschain compatibility. + * @dev Unlike the vanilla OFT Adapter, multiple of these can exist for a given global mesh. + * @dev This contract is meant to be used on Avalanche and other chains that support mint and burn. + */ +contract MockGMX_MintBurnAdapter is MintBurnOFTAdapter, MockOverridableInboundRateLimiter { + using OFTMsgCodec for bytes; + using OFTMsgCodec for bytes32; + + /// @dev The GMX token contract that implements the IGMXMinterBurnable interface + /// @dev Used instead of IMintableBurnable because GMX does not return bool for mint and burn + IGMXMinterBurnable public immutable minterBurnerGMX; + + /// @dev IMinterBurnable is set to address(0) because GMX does not return bool for mint and burn + /// @dev Parent contracts do not use mint() or burn() outside of _credit() and _debit() which are overridden + constructor( + RateLimitConfig[] memory _rateLimitConfigs, + address _token, + address _lzEndpoint, + address _delegate + ) MintBurnOFTAdapter(_token, IMintableBurnable(address(0)), _lzEndpoint, _delegate) Ownable() { + _setRateLimits(_rateLimitConfigs); + minterBurnerGMX = IGMXMinterBurnable(_token); + } + + /** + * @notice Override the base _debit() function to consume rate limit before super._debit() + * @dev This function is called when a debit is made from the OFT. + * @param _from The address from which the debit is made. + * @param _amountLD The amount to debit in local denomination. + * @param _minAmountLD The minimum amount to debit in local denomination. + * @param _dstEid The destination endpoint ID. + */ + function _debit( + address _from, + uint256 _amountLD, + uint256 _minAmountLD, + uint32 _dstEid + ) internal virtual override returns (uint256 amountSentLD, uint256 amountReceivedLD) { + (amountSentLD, amountReceivedLD) = _debitView(_amountLD, _minAmountLD, _dstEid); + + /// @dev Burn the amount being transferred to the destination chain + /// @dev Since GMX does not have a Fee the following invariant holds: + /// @dev `amountSentLD == amountReceivedLD` + minterBurnerGMX.burn(_from, amountSentLD); + + /// @dev While this _technically_ should be amountReceivedLD + /// @dev it can be amountSentLD because GMX does not have a Fee + /// @dev also improves symmetry + _outflowOverridable(_from, amountSentLD, _dstEid); + } + + /** + * @notice Mints tokens to the specified address upon receiving them. + * @param _to The address to credit the tokens to. + * @param _amountLD The amount of tokens to credit in local decimals. + * @return amountReceivedLD The amount of tokens actually received in local decimals. + * @dev WARNING: The default OFTAdapter implementation assumes LOSSLESS transfers, i.e., 1 token in, 1 token out. + * If the 'innerToken' applies something like a transfer fee, the default will NOT work. + * A pre/post balance check will need to be done to calculate the amountReceivedLD. + */ + function _credit( + address _to, + uint256 _amountLD, + uint32 /* _srcEid */ + ) internal virtual override returns (uint256 amountReceivedLD) { + if (_to == address(0x0)) _to = address(0xdead); /// @dev mint(...) does not support address(0x0) + + /// @dev Mints the tokens to the recipient + minterBurnerGMX.mint(_to, _amountLD); + + /// @dev In the case of NON-default OFTAdapter, the amountLD MIGHT not be equal to amountReceivedLD. + return _amountLD; + } + + /** + * @notice Override the base _lzReceive() function to use _inflowOverridable() before super._lzReceive() + * @dev This function is called when a message is received from another chain. + * @param _origin The origin of the message. + * @param _guid The GUID of the message. + * @param _message The message data. + * @param _executor The address of the executor. + * @param _extraData Additional data for the message. + */ + function _lzReceive( + Origin calldata _origin, + bytes32 _guid, + bytes calldata _message, + address _executor, // @dev unused in the default implementation. + bytes calldata _extraData // @dev unused in the default implementation. + ) internal virtual override { + address toAddress = _message.sendTo().bytes32ToAddress(); + + /// @dev We can assume that every layerzero message is an OFT transfer and that there are no non-token messages + _inflowOverridable(_guid, toAddress, _toLD(_message.amountSD()), _origin.srcEid); + + super._lzReceive(_origin, _guid, _message, _executor, _extraData); + } +} diff --git a/contracts/mock/MockGMX_Adapter.sol b/contracts/mock/MockOverridableInboundRateLimiter.sol similarity index 56% rename from contracts/mock/MockGMX_Adapter.sol rename to contracts/mock/MockOverridableInboundRateLimiter.sol index ae39b6f8b..befe0e81e 100644 --- a/contracts/mock/MockGMX_Adapter.sol +++ b/contracts/mock/MockOverridableInboundRateLimiter.sol @@ -2,12 +2,6 @@ pragma solidity ^0.8.22; import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; -import { MintBurnOFTAdapter } from "@layerzerolabs/oft-evm/contracts/MintBurnOFTAdapter.sol"; -import { IMintableBurnable } from "@layerzerolabs/oft-evm/contracts/interfaces/IMintableBurnable.sol"; -import { SendParam, MessagingFee, MessagingReceipt, OFTReceipt } from "@layerzerolabs/oft-evm/contracts/OFTCore.sol"; -import { Origin } from "@layerzerolabs/oapp-evm/contracts/oapp/OApp.sol"; -import { OFTMsgCodec } from "@layerzerolabs/oft-evm/contracts/libs/OFTMsgCodec.sol"; -import { OFTComposeMsgCodec } from "@layerzerolabs/oft-evm/contracts/libs/OFTComposeMsgCodec.sol"; import { RateLimiter } from "@layerzerolabs/oapp-evm/contracts/oapp/utils/RateLimiter.sol"; struct RateLimitExemptAddress { @@ -15,11 +9,10 @@ struct RateLimitExemptAddress { bool isExempt; } -interface IOverridableInboundRatelimit { +interface IOverridableInboundRateLimiter { error InputLengthMismatch(uint256 addressOrGUIDLength, uint256 overridableLength); // 0x6b7f6f0e event RateLimitUpdated(RateLimiter.RateLimitConfig[] newConfigs); - event RateLimitOverrider_ModifiedAddress(RateLimitExemptAddress[] indexed addresses); event RateLimitOverrider_ModifiedGUID(bytes32[] indexed guid, bool canOverride); @@ -56,28 +49,18 @@ interface IOverridableInboundRatelimit { } /** - * @title MintBurnOFTAdapter Contract + * @title OverridableRateLimiter * @author LayerZero Labs (@shankars99) - * @dev MintBurnOFTAdapter is a contract that adapts an ERC-20 token with external mint and burn logic to the OFT functionality. - * @dev For existing ERC20 tokens with exposed mint and burn permissions, this can be used to convert the token to crosschain compatibility. - * @dev Unlike the vanilla OFT Adapter, multiple of these can exist for a given global mesh. + * @dev Abstract contract that provides overridable inbound rate limiting functionality for OFT contracts. + * @dev This contract can be inherited by any LayerZero OFT adapter (MintBurnOFTAdapter, OFTAdapter, NativeOFTAdapter, etc.) + * @dev to add rate limiting with exemption and override capabilities. */ -contract MockGMX_Adapter is MintBurnOFTAdapter, RateLimiter, IOverridableInboundRatelimit { - using OFTMsgCodec for bytes; - using OFTMsgCodec for bytes32; - +abstract contract MockOverridableInboundRateLimiter is RateLimiter, Ownable, IOverridableInboundRateLimiter { + /// @dev Mapping to track addresses exempt from rate limiting mapping(address => bool) public exemptAddresses; - mapping(bytes32 => bool) public guidOverrides; - constructor( - RateLimitConfig[] memory _rateLimitConfigs, - address _token, - IMintableBurnable _minterBurner, - address _lzEndpoint, - address _delegate - ) MintBurnOFTAdapter(_token, _minterBurner, _lzEndpoint, _delegate) Ownable() { - _setRateLimits(_rateLimitConfigs); - } + /// @dev Mapping to track GUIDs that can override rate limiting + mapping(bytes32 => bool) public guidOverrides; /** * @notice Sets the rate limits for the contract. @@ -119,59 +102,36 @@ contract MockGMX_Adapter is MintBurnOFTAdapter, RateLimiter, IOverridableInbound } /** - * @notice Override the base _debit() function to consume rate limit before super._debit() - * @dev This function is called when a debit is made from the OFT. + * @notice Apply rate limiting for outbound transfers (inverted to act as inbound rate limit) + * @dev Uses LayerZero's outbound rate limiter in reverse - calling _inflow() to consume capacity * @param _from The address from which the debit is made. * @param _amountLD The amount to debit in local denomination. - * @param _minAmountLD The minimum amount to debit in local denomination. * @param _dstEid The destination endpoint ID. */ - function _debit( - address _from, - uint256 _amountLD, - uint256 _minAmountLD, - uint32 _dstEid - ) internal virtual override returns (uint256 amountSentLD, uint256 amountReceivedLD) { - /// @dev amountSentLD is amountLD with dust removed - /// @dev amountReceivedLD is amountSentLD with other token amount changes such as fee, etc. - /// @dev GMX does not have any "changes" and so the following is true: - /// amountSentLD = amountReceivedLD - (amountSentLD, amountReceivedLD) = super._debit(_from, _amountLD, _minAmountLD, _dstEid); - - /// @dev If the sender is an exemptAddress (FeeDistributor) then we do NOT refill the rate limiter. - if (!exemptAddresses[msg.sender]) { - /// @dev The original layerzero rate limiter is an outbound rate limit. + function _outflowOverridable(address _from, uint256 _amountLD, uint32 _dstEid) internal virtual { + /// @dev Apply outbound rate limiting if sender is not exempt + if (!exemptAddresses[_from]) { + /// @dev The original LayerZero rate limiter is an outbound rate limit. /// @dev A unidirectional graph can be inverted by swapping the inflow and outflow functions. /// @dev This makes the rate limiter an inbound rate limit. - super._inflow(_dstEid, amountReceivedLD); + super._inflow(_dstEid, _amountLD); } } /** - * @notice Override the base _lzReceive() function to use _inflowOverridable() before super._lzReceive() - * @dev This function is called when a message is received from another chain. - * @param _origin The origin of the message. + * @notice Apply rate limiting for inbound transfers (inverted to act as inbound rate limit) + * @dev Uses LayerZero's outbound rate limiter in reverse - calling _outflow() to consume capacity * @param _guid The GUID of the message. - * @param _message The message data. - * @param _executor The address of the executor. - * @param _extraData Additional data for the message. + * @param _to The address of the recipient. + * @param _amountLD The amount of tokens received in local decimals. + * @param _srcEid The source chain ID. */ - function _lzReceive( - Origin calldata _origin, - bytes32 _guid, - bytes calldata _message, - address _executor, // @dev unused in the default implementation. - bytes calldata _extraData // @dev unused in the default implementation. - ) internal virtual override { - address toAddress = _message.sendTo().bytes32ToAddress(); - - /// @dev If the address is exempt or the GUID is overridable, skip the rate limit check else apply the rate limit. - if (!exemptAddresses[toAddress] && !guidOverrides[_guid]) { - /// @dev The original layerzero rate limiter is an outbound rate limit. + function _inflowOverridable(bytes32 _guid, address _to, uint256 _amountLD, uint32 _srcEid) internal virtual { + /// @dev Apply inbound rate limiting if recipient is not exempt and GUID is not overridable + if (!exemptAddresses[_to] && !guidOverrides[_guid]) { + /// @dev The original LayerZero rate limiter is an outbound rate limit. /// @dev Switching `inflow` and `outflow` makes the rate limiter an inbound rate limit. - super._outflow(_origin.srcEid, _toLD(_message.amountSD())); + super._outflow(_srcEid, _amountLD); } - - super._lzReceive(_origin, _guid, _message, _executor, _extraData); } } diff --git a/scripts/bridgedGmxReceivedLocalhost.ts b/scripts/bridgedGmxReceivedLocalhost.ts index d52e947ae..3f36dbaae 100644 --- a/scripts/bridgedGmxReceivedLocalhost.ts +++ b/scripts/bridgedGmxReceivedLocalhost.ts @@ -54,33 +54,30 @@ async function main() { const mockEndpointV2C = await deployContract("MockEndpointV2", [eidC]); const gmxA = await deployContract("MintableToken", ["GMX", "GMX", 18]); const gmxC = await deployContract("MintableToken", ["GMX", "GMX", 18]); - const mockGmxAdapterA = await deployContract("MockGMX_Adapter", [ + const mockGmxAdapterA = await deployContract("MockGMX_MintBurnAdapter", [ [ { dstEid: eidB, limit: expandDecimals(1000000, 18), window: 60 }, { dstEid: eidC, limit: expandDecimals(1000000, 18), window: 60 }, ], gmxA.address, - gmxA.address, mockEndpointV2A.address, accounts[0].address, ]); - const mockGmxAdapterB = await deployContract("MockGMX_Adapter", [ + const mockGmxAdapterB = await deployContract("MockGMX_LockboxAdapter", [ [ { dstEid: eidA, limit: expandDecimals(1000000, 18), window: 60 }, { dstEid: eidC, limit: expandDecimals(1000000, 18), window: 60 }, ], gmx.address, - gmx.address, mockEndpointV2B.address, accounts[0].address, ]); - const mockGmxAdapterC = await deployContract("MockGMX_Adapter", [ + const mockGmxAdapterC = await deployContract("MockGMX_MintBurnAdapter", [ [ { dstEid: eidA, limit: expandDecimals(1000000, 18), window: 60 }, { dstEid: eidB, limit: expandDecimals(1000000, 18), window: 60 }, ], gmxC.address, - gmxC.address, mockEndpointV2C.address, accounts[0].address, ]); @@ -278,6 +275,7 @@ async function main() { await gmx.mint(feeHandler.address, expandDecimals(10_000, 18)); await gmx.mint(accounts[0].address, expandDecimals(170_000, 18)); + await gmx.approve(mockGmxAdapterB.address, expandDecimals(130_000, 18)); let sendParam = { dstEid: eidA, to: addressToBytes32(accounts[1].address), diff --git a/scripts/feeDistributorConfigTestnet.ts b/scripts/feeDistributorConfigTestnet.ts index 951ef5a95..e3a8f8cf8 100644 --- a/scripts/feeDistributorConfigTestnet.ts +++ b/scripts/feeDistributorConfigTestnet.ts @@ -1,7 +1,7 @@ import { ethers } from "hardhat"; import * as fs from "fs"; import * as path from "path"; -import { Contract, ContractFactory, Signer } from "ethers"; +import { Contract, ContractFactory, Signer, BigNumber } from "ethers"; import * as keys from "../utils/keys"; import { hashString, encodeData } from "../utils/hash"; @@ -13,6 +13,7 @@ type ChainConfig = { chainIds: number[]; eids: number[]; channelId: number; + wntTargetBalance: BigNumber; }; type DeploymentData = { @@ -42,7 +43,7 @@ if (resetDistributionTimestampStr !== "true" && resetDistributionTimestampStr != } const resetDistributionTimestamp = resetDistributionTimestampStr === "true"; -const ZERO = ethers.BigNumber.from(0); +const ZERO = BigNumber.from(0); const CHAIN_PAIRS = { localhost: { @@ -53,6 +54,10 @@ const CHAIN_PAIRS = { arbitrum: { chainId: 421614, network: "arbitrumSepolia", eid: 40231 }, base: { chainId: 84532, network: "baseSepolia", eid: 40245 }, }, + mainnet: { + arbitrum: { chainId: 42161, network: "arbitrum", eid: 30110 }, + avalanche: { chainId: 43114, network: "avalanche", eid: 30106 }, + }, }; const SCENARIOS: { [key: string]: TestScenario } = { @@ -78,24 +83,7 @@ async function delay(ms: number) { return new Promise((resolve) => setTimeout(resolve, ms)); } -async function getDeployer() { - const network = process.env.HARDHAT_NETWORK || "localhost"; - - if (network === "localhost") { - const [deployer] = await ethers.getSigners(); - return deployer; - } else { - const privateKey = process.env.DEPLOYER_PRIVATE_KEY; - if (!privateKey) { - throw new Error("DEPLOYER_PRIVATE_KEY not set in .env file"); - } - const deployer = new ethers.Wallet(privateKey, ethers.provider); - return deployer; - } -} - -async function getFactory(contractName: string, libraries?: any) { - const deployer = await getDeployer(); +async function getFactory(deployer: ethers.SignerWithAddress, contractName: string, libraries?: any) { if (libraries) { return await ethers.getContractFactory(contractName, { signer: deployer, @@ -105,7 +93,7 @@ async function getFactory(contractName: string, libraries?: any) { return await ethers.getContractFactory(contractName, deployer); } -async function loadDeployment(network: string, tag?: string): Promise { +async function loadDeployment(network: string, tag: string): Promise { const deploymentsDir = path.join(__dirname, "../deployments"); if (!fs.existsSync(deploymentsDir)) { @@ -114,7 +102,7 @@ async function loadDeployment(network: string, tag?: string): Promise f.startsWith(searchPattern)); if (deploymentFiles.length === 0) { @@ -128,16 +116,16 @@ async function loadDeployment(network: string, tag?: string): Promise { console.log("\nSetting up test data"); console.log(`Scenario: ${scenario.scenario} chain\n`); - const deployDelay = network === "localhost" ? 0 : 5000; + const txDelay = network === "localhost" ? 0 : 1000; - const DataStore = await getFactory("DataStore"); + const DataStore = await getFactory(deployer, "DataStore"); const dataStore = DataStore.attach(contracts.dataStore); - const MintableToken = await getFactory("MintableToken"); + const MintableToken = await getFactory(deployer, "MintableToken"); const gmx = MintableToken.attach(contracts.gmx); const wnt = MintableToken.attach(contracts.wnt); const esGmx = MintableToken.attach(contracts.esGmx); - const MockRewardTrackerV1 = await getFactory("MockRewardTrackerV1"); + const MockRewardTrackerV1 = await getFactory(deployer, "MockRewardTrackerV1"); const mockExtendedGmxTracker = MockRewardTrackerV1.attach(contracts.mockExtendedGmxTracker); // Set up mock staked GMX amount await mockExtendedGmxTracker.setTotalSupply(scenario.stakedGmxAmount); console.log(`Set staked GMX: ${ethers.utils.formatEther(scenario.stakedGmxAmount)}`); - await delay(deployDelay); + await delay(txDelay); // Set withdrawable buyback amount await dataStore.setUint(keys.withdrawableBuybackTokenAmountKey(contracts.gmx), scenario.withdrawableBuybackAmount); console.log(`Set withdrawable GMX: ${ethers.utils.formatEther(scenario.withdrawableBuybackAmount)}`); - await delay(deployDelay); + await delay(txDelay); // Mint GMX to FeeHandler let currentBalance = await gmx.balanceOf(contracts.feeHandler); if (currentBalance > ZERO) { await gmx.burn(contracts.feeHandler, currentBalance); console.log(`Burned GMX in FeeHandler: ${ethers.utils.formatEther(currentBalance)}`); - await delay(deployDelay); + await delay(txDelay); } await gmx.mint(contracts.feeHandler, scenario.withdrawableBuybackAmount); console.log(`Minted GMX to FeeHandler: ${ethers.utils.formatEther(scenario.withdrawableBuybackAmount)}`); - await delay(deployDelay); + await delay(txDelay); // Mint GMX to FeeDistributorVault currentBalance = await gmx.balanceOf(contracts.feeDistributorVault); if (currentBalance > ZERO) { await gmx.burn(contracts.feeDistributorVault, currentBalance); console.log(`Burned GMX in FeeDistributorVault: ${ethers.utils.formatEther(currentBalance)}`); - await delay(deployDelay); + await delay(txDelay); } await gmx.mint(contracts.feeDistributorVault, scenario.vaultGmxAmount); console.log(`Minted GMX to FeeDistributorVault: ${ethers.utils.formatEther(scenario.vaultGmxAmount)}`); - await delay(deployDelay); + await delay(txDelay); // Mint WNT to FeeDistributorVault currentBalance = await wnt.balanceOf(contracts.feeDistributorVault); if (currentBalance > ZERO) { await wnt.burn(contracts.feeDistributorVault, currentBalance); console.log(`Burned WNT in FeeDistributorVault: ${ethers.utils.formatEther(currentBalance)}`); - await delay(deployDelay); + await delay(txDelay); } await wnt.mint(contracts.feeDistributorVault, scenario.wntAmount); console.log(`Minted WNT to FeeDistributorVault: ${ethers.utils.formatEther(scenario.wntAmount)}`); - await delay(deployDelay); + await delay(txDelay); // Mint some esGMX to FeeDistributorVault for referral rewards currentBalance = await esGmx.balanceOf(contracts.feeDistributorVault); if (currentBalance > ZERO) { await esGmx.burn(contracts.feeDistributorVault, currentBalance); console.log(`Burned esGMX in FeeDistributorVault: ${ethers.utils.formatEther(currentBalance)}`); - await delay(deployDelay); + await delay(txDelay); } await esGmx.mint(contracts.feeDistributorVault, expandDecimals(10, 18)); console.log("Minted 10 esGMX to FeeDistributorVault for referral rewards"); - await delay(deployDelay); + await delay(txDelay); // Fund FeeDistributor with ETH for gas currentBalance = await ethers.provider.getBalance(contracts.feeDistributor); console.log("Current FeeDistributor ETH balance: ", ethers.utils.formatEther(currentBalance)); - const sendAmount = expandDecimals(1, 17).sub(currentBalance); + const sendAmount = wntTargetBalance.sub(currentBalance); console.log("ETH send amount: ", ethers.utils.formatEther(sendAmount)); await deployer.sendTransaction({ to: contracts.feeDistributor, value: sendAmount, }); - console.log(`Funded FeeDistributor with ${ethers.utils.formatEther(sendAmount)} ETH for gas for a total of 0.1 ETH`); - await delay(deployDelay); + console.log( + `Funded FeeDistributor with ${ethers.utils.formatEther( + sendAmount + )} ETH for gas for a total of ${ethers.utils.formatEther(wntTargetBalance)} WNT` + ); + await delay(txDelay); // Calculate expected distribution - const totalFees = ethers.BigNumber.from(scenario.withdrawableBuybackAmount).add(scenario.vaultGmxAmount); - const stakedGmx = ethers.BigNumber.from(scenario.stakedGmxAmount); + const totalFees = BigNumber.from(scenario.withdrawableBuybackAmount).add(scenario.vaultGmxAmount); + const stakedGmx = BigNumber.from(scenario.stakedGmxAmount); const feePerStakedGmx = totalFees.mul(expandDecimals(1, 18)).div(stakedGmx); console.log("\nTest Data Summary:"); - console.log(`Network: ${network}${deploymentTag ? ` (${deploymentTag})` : ""}`); + console.log(`Network: ${network}${DEPLOYMENT_TAG ? ` (${DEPLOYMENT_TAG})` : ""}`); console.log(`Scenario: ${scenario.scenario} chain`); console.log(`Staked GMX: ${ethers.utils.formatEther(scenario.stakedGmxAmount)}`); console.log(`Withdrawable GMX: ${ethers.utils.formatEther(scenario.withdrawableBuybackAmount)}`); @@ -280,19 +287,19 @@ async function setupTestData( async function configureContracts( chainConfig: ChainConfig ): Promise<{ deployment: DeploymentData; config: ChainConfig }> { - const deployer = await getDeployer(); + const [deployer] = await ethers.getSigners(); const deployerAddress = await deployer.getAddress(); const network = process.env.HARDHAT_NETWORK || "localhost"; - const deploymentTag = process.env.DEPLOYMENT_TAG || ""; + const DEPLOYMENT_TAG = process.env.DEPLOYMENT_TAG || ""; const setupData = process.env.SETUP_DATA || ""; const scenarioOverride = process.env.SCENARIO || ""; - const deployDelay = network === "localhost" ? 0 : 2000; + const txDelay = 5000; console.log("\nConfiguring contracts"); console.log("Network:", network); - if (deploymentTag) { - console.log("Deployment tag:", deploymentTag); + if (DEPLOYMENT_TAG) { + console.log("Deployment tag:", DEPLOYMENT_TAG); } console.log("Account:", deployerAddress); if (setupData) { @@ -300,14 +307,14 @@ async function configureContracts( } // Load current deployment - const deployment = await loadDeployment(network, deploymentTag); + const deployment = await loadDeployment(network, DEPLOYMENT_TAG); if (!deployment) { - throw new Error(`No deployment found for ${network}${deploymentTag ? ` (${deploymentTag})` : ""}`); + throw new Error(`No deployment found for ${network}${DEPLOYMENT_TAG ? ` (${DEPLOYMENT_TAG})` : ""}`); } const contracts = deployment.contracts; // Try to load other chain's deployment - const otherChainInfo = determineOtherChain(network, chainConfig.currentChainId, deploymentTag); + const otherChainInfo = determineOtherChain(network, chainConfig.currentChainId, DEPLOYMENT_TAG); let otherContracts = null; if (otherChainInfo) { @@ -325,14 +332,14 @@ async function configureContracts( } // Get contract instances - const Config: ContractFactory = await getFactory("Config", { + const Config: ContractFactory = await getFactory(deployer, "Config", { libraries: { ConfigUtils: contracts.configUtils, }, }); const config: Contract = Config.attach(contracts.config); - const DataStore: ContractFactory = await getFactory("DataStore"); + const DataStore: ContractFactory = await getFactory(deployer, "DataStore"); const dataStore: Contract = DataStore.attach(contracts.dataStore); console.log("\n1. Configuring Oracle Price Feeds..."); @@ -340,12 +347,12 @@ async function configureContracts( // Enable the ChainlinkPriceFeedProvider as an oracle provider await dataStore.setBool(keys.isOracleProviderEnabledKey(contracts.chainlinkPriceFeedProvider), true); console.log("Enabled ChainlinkPriceFeedProvider as oracle provider"); - await delay(deployDelay); + await delay(txDelay); // Set as atomic oracle provider await dataStore.setBool(keys.isAtomicOracleProviderKey(contracts.chainlinkPriceFeedProvider), true); console.log("Set ChainlinkPriceFeedProvider as atomic oracle provider"); - await delay(deployDelay); + await delay(txDelay); // Configure oracle providers for tokens await dataStore.setAddress( @@ -353,42 +360,42 @@ async function configureContracts( contracts.chainlinkPriceFeedProvider ); console.log("Set oracle provider for WNT"); - await delay(deployDelay); + await delay(txDelay); await dataStore.setAddress( keys.oracleProviderForTokenKey(contracts.oracle, contracts.gmx), contracts.chainlinkPriceFeedProvider ); console.log("Set oracle provider for GMX"); - await delay(deployDelay); + await delay(txDelay); // Configure price feed addresses directly in DataStore await dataStore.setAddress(keys.priceFeedKey(contracts.wnt), contracts.wethPriceFeed); console.log("Set WETH price feed in DataStore"); - await delay(deployDelay); + await delay(txDelay); await dataStore.setAddress(keys.priceFeedKey(contracts.gmx), contracts.gmxPriceFeed); console.log("Set GMX price feed in DataStore"); - await delay(deployDelay); + await delay(txDelay); // Set price feed multipliers await dataStore.setUint(keys.priceFeedMultiplierKey(contracts.wnt), expandDecimals(1, 34)); - await delay(deployDelay); + await delay(txDelay); await dataStore.setUint(keys.priceFeedMultiplierKey(contracts.gmx), expandDecimals(1, 34)); console.log("Set price feed multipliers"); - await delay(deployDelay); + await delay(txDelay); // Set heartbeat durations await dataStore.setUint( keys.priceFeedHeartbeatDurationKey(contracts.wnt), 3600 // 1 hour ); - await delay(deployDelay); + await delay(txDelay); await dataStore.setUint(keys.priceFeedHeartbeatDurationKey(contracts.gmx), 3600); console.log("Set heartbeat durations"); - await delay(deployDelay); + await delay(txDelay); console.log("Oracle price feeds configured"); @@ -398,35 +405,35 @@ async function configureContracts( const distributionDay = 3; // Wednesday await config.setUint(keys.FEE_DISTRIBUTOR_DISTRIBUTION_DAY, "0x", distributionDay); console.log("Set distribution day to:", distributionDay); - await delay(deployDelay); + await delay(txDelay); await dataStore.setUint(keys.FEE_DISTRIBUTOR_STATE, 0); - await delay(deployDelay); + await delay(txDelay); await config.setUint(keys.FEE_DISTRIBUTOR_V1_FEES_WNT_FACTOR, "0x", expandDecimals(70, 28)); - await delay(deployDelay); + await delay(txDelay); await config.setUint(keys.FEE_DISTRIBUTOR_V2_FEES_WNT_FACTOR, "0x", expandDecimals(10, 28)); - await delay(deployDelay); + await delay(txDelay); await config.setUint(keys.FEE_DISTRIBUTOR_MAX_REFERRAL_REWARDS_WNT_USD_AMOUNT, "0x", expandDecimals(1_000_000, 30)); - await delay(deployDelay); + await delay(txDelay); await config.setUint(keys.FEE_DISTRIBUTOR_MAX_REFERRAL_REWARDS_ESGMX_AMOUNT, "0x", expandDecimals(5000, 18)); - await delay(deployDelay); + await delay(txDelay); await config.setUint(keys.FEE_DISTRIBUTOR_MAX_READ_RESPONSE_DELAY, "0x", 259200); // 3 days - await delay(deployDelay); + await delay(txDelay); await config.setUint(keys.FEE_DISTRIBUTOR_GAS_LIMIT, "0x", 5_000_000); - await delay(deployDelay); + await delay(txDelay); await config.setUint(keys.FEE_DISTRIBUTOR_MAX_REFERRAL_REWARDS_WNT_USD_FACTOR, "0x", expandDecimals(20, 28)); - await delay(deployDelay); + await delay(txDelay); await config.setUint(keys.FEE_DISTRIBUTOR_MAX_WNT_AMOUNT_FROM_TREASURY, "0x", expandDecimals(1, 16)); - await delay(deployDelay); + await delay(txDelay); await config.setUint(keys.FEE_DISTRIBUTOR_CHAINLINK_FACTOR, "0x", expandDecimals(12, 28)); - await delay(deployDelay); + await delay(txDelay); console.log("Basic fee distribution parameters configured"); // Configure chain IDs await dataStore.setUintArray(keys.FEE_DISTRIBUTOR_CHAIN_ID, chainConfig.chainIds); console.log("Set chain IDs:", chainConfig.chainIds); - await delay(deployDelay); + await delay(txDelay); // Configure LayerZero endpoint IDs for each chain for (let i = 0; i < chainConfig.chainIds.length; i++) { @@ -435,7 +442,7 @@ async function configureContracts( await config.setUint(keys.FEE_DISTRIBUTOR_LAYERZERO_CHAIN_ID, encodeData(["uint256"], [chainId]), eid); console.log(`Set LayerZero endpoint ID for chain ${chainId}: ${eid}`); - await delay(deployDelay); + await delay(txDelay); } // Configure addresses for each chain @@ -473,28 +480,28 @@ async function configureContracts( encodeData(["uint256", "bytes32"], [chainId, hashString("GMX")]), gmxAddress ); - await delay(deployDelay); + await delay(txDelay); await config.setAddress( keys.FEE_DISTRIBUTOR_ADDRESS_INFO_FOR_CHAIN, encodeData(["uint256", "bytes32"], [chainId, hashString("EXTENDED_GMX_TRACKER")]), trackerAddress ); - await delay(deployDelay); + await delay(txDelay); await config.setAddress( keys.FEE_DISTRIBUTOR_ADDRESS_INFO_FOR_CHAIN, encodeData(["uint256", "bytes32"], [chainId, hashString("DATASTORE")]), dataStoreAddress ); - await delay(deployDelay); + await delay(txDelay); await config.setAddress( keys.FEE_DISTRIBUTOR_ADDRESS_INFO_FOR_CHAIN, encodeData(["uint256", "bytes32"], [chainId, keys.FEE_RECEIVER]), feeReceiverAddress ); - await delay(deployDelay); + await delay(txDelay); console.log(`Configured addresses for chain ${chainId} (${isCurrentChain ? "current" : "other"})`); } else { @@ -507,13 +514,13 @@ async function configureContracts( encodeData(["uint256"], [chainId]), expandDecimals(99, 28) // 99% ); - await delay(deployDelay); + await delay(txDelay); } // Set current chain fee receiver await dataStore.setAddress(keys.FEE_RECEIVER, contracts.feeDistributorVault); console.log("Set fee receiver to:", contracts.feeDistributorVault); - await delay(deployDelay); + await delay(txDelay); // Configure general addresses - use gmxAdapter from deployment await config.setAddress( @@ -521,28 +528,28 @@ async function configureContracts( encodeData(["bytes32"], [hashString("LAYERZERO_OFT")]), contracts.gmxAdapter ); - await delay(deployDelay); + await delay(txDelay); await config.setAddress( keys.FEE_DISTRIBUTOR_ADDRESS_INFO, encodeData(["bytes32"], [hashString("CHAINLINK")]), deployerAddress ); - await delay(deployDelay); + await delay(txDelay); await config.setAddress( keys.FEE_DISTRIBUTOR_ADDRESS_INFO, encodeData(["bytes32"], [hashString("TREASURY")]), deployerAddress ); - await delay(deployDelay); + await delay(txDelay); await config.setAddress( keys.FEE_DISTRIBUTOR_ADDRESS_INFO, encodeData(["bytes32"], [hashString("ESGMX_VESTER")]), contracts.mockVester ); - await delay(deployDelay); + await delay(txDelay); console.log("General addresses configured"); @@ -554,27 +561,27 @@ async function configureContracts( const keepersV2 = [true]; await dataStore.setAddressArray(keys.FEE_DISTRIBUTOR_KEEPER_COSTS, keeperAddresses); - await delay(deployDelay); + await delay(txDelay); await dataStore.setUintArray(keys.FEE_DISTRIBUTOR_KEEPER_COSTS, keeperTargetBalances); - await delay(deployDelay); + await delay(txDelay); await dataStore.setBoolArray(keys.FEE_DISTRIBUTOR_KEEPER_COSTS, keepersV2); - await delay(deployDelay); + await delay(txDelay); console.log("Keeper costs configured"); // Configure buyback amounts await config.setUint(keys.BUYBACK_BATCH_AMOUNT, encodeData(["address"], [contracts.gmx]), expandDecimals(5, 17)); - await delay(deployDelay); + await delay(txDelay); await config.setUint(keys.BUYBACK_BATCH_AMOUNT, encodeData(["address"], [contracts.wnt]), expandDecimals(5, 17)); - await delay(deployDelay); + await delay(txDelay); // Configure token transfer gas limits await dataStore.setUint(keys.tokenTransferGasLimit(contracts.gmx), 200_000); - await delay(deployDelay); + await delay(txDelay); await dataStore.setUint(keys.tokenTransferGasLimit(contracts.wnt), 200_000); - await delay(deployDelay); + await delay(txDelay); await dataStore.setUint(keys.tokenTransferGasLimit(contracts.esGmx), 200_000); - await delay(deployDelay); + await delay(txDelay); // Configure MultichainReader console.log("\n5. Configuring MultichainReader..."); @@ -584,17 +591,17 @@ async function configureContracts( encodeData(["address"], [contracts.feeDistributor]), true ); - await delay(deployDelay); + await delay(txDelay); await config.setUint(keys.MULTICHAIN_READ_CHANNEL, "0x", chainConfig.channelId); - await delay(deployDelay); + await delay(txDelay); await config.setBytes32( keys.MULTICHAIN_PEERS, encodeData(["uint256"], [chainConfig.channelId]), ethers.utils.hexZeroPad(contracts.multichainReader, 32) ); - await delay(deployDelay); + await delay(txDelay); // Set confirmations for each endpoint for (const eid of chainConfig.eids) { @@ -603,7 +610,7 @@ async function configureContracts( encodeData(["uint256"], [eid]), 1 // Number of confirmations ); - await delay(deployDelay); + await delay(txDelay); } console.log("MultichainReader configured"); @@ -612,7 +619,7 @@ async function configureContracts( if (network === "localhost") { console.log("\n6. Configuring mock endpoints for local testing..."); - const MockEndpointV2: ContractFactory = await getFactory("MockEndpointV2"); + const MockEndpointV2: ContractFactory = await getFactory(deployer, "MockEndpointV2"); // Configure the MultichainReader's endpoint (uses separate endpoint to avoid reentrancy) const mockEndpointMultichainReader: Contract = MockEndpointV2.attach(contracts.mockEndpointMultichainReader); @@ -620,29 +627,29 @@ async function configureContracts( contracts.multichainReader, mockEndpointMultichainReader.address ); - await delay(deployDelay); + await delay(txDelay); await mockEndpointMultichainReader.setReadChannelId(chainConfig.channelId); console.log("Configured MockEndpointV2 for MultichainReader"); - await delay(deployDelay); + await delay(txDelay); const mockEndpointGmxAdapter: Contract = MockEndpointV2.attach(contracts.mockEndpointGmxAdapter); await mockEndpointGmxAdapter.setDestLzEndpoint(otherContracts.gmxAdapter, otherContracts.mockEndpointGmxAdapter); console.log("Configured MockEndpointV2 for GmxAdapter"); console.log("Mock endpoints configured"); - await delay(deployDelay); + await delay(txDelay); } - const MockGMXAdapter: ContractFactory = await getFactory("MockGMX_Adapter"); + const MockGMXAdapter: ContractFactory = await getFactory("MockGMX_MintBurnAdapter"); const gmxAdapter: Contract = MockGMXAdapter.attach(contracts.gmxAdapter); await gmxAdapter.setPeer(otherChainInfo.otherEid, ethers.utils.zeroPad(otherContracts.gmxAdapter, 32)); - await delay(deployDelay); + await delay(txDelay); await gmxAdapter.setEnforcedOptions([{ eid: otherChainInfo.otherEid, msgType: 1, options: options }]); - await delay(deployDelay); + await delay(txDelay); // Reset distribution timestamp if resetDistributionTimestamp = true if (resetDistributionTimestamp) { await dataStore.setUint(keys.FEE_DISTRIBUTOR_DISTRIBUTION_TIMESTAMP, 0); - await delay(deployDelay); + await delay(txDelay); } console.log("\nConfiguration complete!"); @@ -660,15 +667,15 @@ async function configureContracts( if (scenarioOverride && SCENARIOS[scenarioOverride]) { // Use explicitly specified scenario scenario = SCENARIOS[scenarioOverride]; - } else if (network === "localhost" && deploymentTag) { + } else if (network === "localhost" && DEPLOYMENT_TAG) { // For localhost, chainA gets deficit, chainB gets surplus by default - scenario = deploymentTag === "chainA" ? SCENARIOS.deficit : SCENARIOS.surplus; + scenario = DEPLOYMENT_TAG === "chainA" ? SCENARIOS.deficit : SCENARIOS.surplus; } else { - // For testnets, use scenario override or default to surplus + // For testnets and mainnets, use scenario override or default to surplus scenario = SCENARIOS[scenarioOverride] || SCENARIOS.surplus; } - await setupTestData(contracts, scenario, deployer, network, deploymentTag); + await setupTestData(contracts, scenario, deployer, network, chainConfig.wntTargetBalance, DEPLOYMENT_TAG); console.log("\nTest data setup complete!"); } @@ -681,24 +688,32 @@ async function configureContracts( // Run configuration async function main(): Promise { const network = process.env.HARDHAT_NETWORK || "localhost"; - const deploymentTag = process.env.DEPLOYMENT_TAG || ""; + const DEPLOYMENT_TAG = process.env.DEPLOYMENT_TAG; + if (!DEPLOYMENT_TAG) { + throw new Error("Environment variable DEPLOYMENT_TAG not set"); + } + if (DEPLOYMENT_TAG !== "chainA" && DEPLOYMENT_TAG !== "chainB") { + throw new Error('Environment variable DEPLOYMENT_TAG must equal "chainA" or "chainB"'); + } let chainConfig: ChainConfig; if (network === "localhost") { - if (deploymentTag === "chainA") { + if (DEPLOYMENT_TAG === "chainA") { chainConfig = { currentChainId: CHAIN_PAIRS.localhost.chainA.chainId, chainIds: [CHAIN_PAIRS.localhost.chainA.chainId, CHAIN_PAIRS.localhost.chainB.chainId], eids: [CHAIN_PAIRS.localhost.chainA.eid, CHAIN_PAIRS.localhost.chainB.eid], channelId: 1001, + wntTargetBalance: expandDecimals(1, 17), }; - } else if (deploymentTag === "chainB") { + } else if (DEPLOYMENT_TAG === "chainB") { chainConfig = { currentChainId: CHAIN_PAIRS.localhost.chainB.chainId, chainIds: [CHAIN_PAIRS.localhost.chainA.chainId, CHAIN_PAIRS.localhost.chainB.chainId], eids: [CHAIN_PAIRS.localhost.chainA.eid, CHAIN_PAIRS.localhost.chainB.eid], channelId: 1001, + wntTargetBalance: expandDecimals(1, 17), }; } else { throw new Error("For localhost, DEPLOYMENT_TAG must be 'chainA' or 'chainB'"); @@ -709,6 +724,7 @@ async function main(): Promise { chainIds: [CHAIN_PAIRS.testnet.arbitrum.chainId, CHAIN_PAIRS.testnet.base.chainId], eids: [CHAIN_PAIRS.testnet.arbitrum.eid, CHAIN_PAIRS.testnet.base.eid], channelId: 4294967295, + wntTargetBalance: expandDecimals(1, 16), }; } else if (network === "baseSepolia") { chainConfig = { @@ -716,6 +732,23 @@ async function main(): Promise { chainIds: [CHAIN_PAIRS.testnet.arbitrum.chainId, CHAIN_PAIRS.testnet.base.chainId], eids: [CHAIN_PAIRS.testnet.arbitrum.eid, CHAIN_PAIRS.testnet.base.eid], channelId: 4294967295, + wntTargetBalance: expandDecimals(1, 16), + }; + } else if (network === "arbitrum") { + chainConfig = { + currentChainId: CHAIN_PAIRS.mainnet.arbitrum.chainId, + chainIds: [CHAIN_PAIRS.mainnet.arbitrum.chainId, CHAIN_PAIRS.mainnet.avalanche.chainId], + eids: [CHAIN_PAIRS.mainnet.arbitrum.eid, CHAIN_PAIRS.mainnet.avalanche.eid], + channelId: 4294967295, + wntTargetBalance: expandDecimals(5, 14), + }; + } else if (network === "avalanche") { + chainConfig = { + currentChainId: CHAIN_PAIRS.mainnet.avalanche.chainId, + chainIds: [CHAIN_PAIRS.mainnet.arbitrum.chainId, CHAIN_PAIRS.mainnet.avalanche.chainId], + eids: [CHAIN_PAIRS.mainnet.arbitrum.eid, CHAIN_PAIRS.mainnet.avalanche.eid], + channelId: 4294967295, + wntTargetBalance: expandDecimals(1, 17), }; } else { throw new Error(`Network ${network} not configured`); diff --git a/scripts/feeDistributorDeployTestnet.ts b/scripts/feeDistributorDeployTestnet.ts index a3c5292d0..6cb03e009 100644 --- a/scripts/feeDistributorDeployTestnet.ts +++ b/scripts/feeDistributorDeployTestnet.ts @@ -5,7 +5,13 @@ import { Contract, ContractFactory } from "ethers"; import { hashString } from "../utils/hash"; import { expandDecimals } from "../utils/math"; -const DEPLOYMENT_TAG = process.env.DEPLOYMENT_TAG || ""; +const DEPLOYMENT_TAG = process.env.DEPLOYMENT_TAG; +if (!DEPLOYMENT_TAG) { + throw new Error("Environment variable DEPLOYMENT_TAG not set"); +} +if (DEPLOYMENT_TAG !== "chainA" && DEPLOYMENT_TAG !== "chainB") { + throw new Error('Environment variable DEPLOYMENT_TAG must equal "chainA" or "chainB"'); +} type NetworkConfig = { chainId: number; @@ -37,7 +43,7 @@ function saveCheckpoint(step: number, contracts: { [key: string]: string }) { `../deployments/checkpoint-${network}${DEPLOYMENT_TAG ? `-${DEPLOYMENT_TAG}` : ""}.json` ); fs.writeFileSync(checkpointPath, JSON.stringify({ step, contracts }, null, 2)); - console.log(`✓ Checkpoint saved at step ${step}`); + console.log(`Checkpoint saved at step ${step}`); } function loadCheckpoint(): DeploymentCheckpoint | null { @@ -60,7 +66,7 @@ function clearCheckpoint() { ); if (fs.existsSync(checkpointPath)) { fs.unlinkSync(checkpointPath); - console.log("✓ Checkpoint cleared"); + console.log("Checkpoint cleared"); } } @@ -90,30 +96,23 @@ const NETWORK_CONFIG: { [key: string]: NetworkConfig } = { channelId: 4294967295, lzEndpoint: "0x6EDCE65403992e310A62460808c4b910D972f10f", // LayerZero V2 Endpoint on Base Sepolia }, + arbitrum: { + chainId: 42161, + eid: 30110, + counterEid: 30106, + channelId: 4294967295, + lzEndpoint: "0x1a44076050125825900e736c501f859c50fE728c", // LayerZero V2 Endpoint on Arbitrum + }, + avalanche: { + chainId: 43114, + eid: 30106, + counterEid: 30110, + channelId: 4294967295, + lzEndpoint: "0x1a44076050125825900e736c501f859c50fE728c", // LayerZero V2 Endpoint on Avalanche + }, }; -async function getDeployer() { - const network = process.env.HARDHAT_NETWORK || "localhost"; - - if (network === "localhost") { - // For localhost, use the default signer - const [deployer] = await ethers.getSigners(); - return deployer; - } else { - // For testnets/mainnet, use private key from env - const privateKey = process.env.DEPLOYER_PRIVATE_KEY; - if (!privateKey) { - throw new Error("DEPLOYER_PRIVATE_KEY not set in .env file"); - } - - // Create wallet from private key - const deployer = new ethers.Wallet(privateKey, ethers.provider); - return deployer; - } -} - -async function getFactory(contractName: string, libraries?: any) { - const deployer = await getDeployer(); +async function getFactory(deployer: ethers.SignerWithAddress, contractName: string, libraries?: any) { if (libraries) { return await ethers.getContractFactory(contractName, { signer: deployer, @@ -124,7 +123,7 @@ async function getFactory(contractName: string, libraries?: any) { } async function deployContracts(): Promise { - const deployer = await getDeployer(); + const [deployer] = await ethers.getSigners(); const deployerAddress = await deployer.getAddress(); console.log("Deploying contracts with account:", deployerAddress); @@ -138,7 +137,7 @@ async function deployContracts(): Promise { console.log(`Deploying on network: ${network}`); console.log("Config:", config); - const deployDelay = network === "localhost" ? 0 : 2000; + const txDelay = network === "localhost" ? 0 : 1000; // Load checkpoint if exists const checkpoint = loadCheckpoint(); @@ -153,43 +152,38 @@ async function deployContracts(): Promise { console.log("\n1. Deploying core infrastructure..."); // RoleStore - const RoleStore: ContractFactory = await getFactory("RoleStore"); + const RoleStore: ContractFactory = await getFactory(deployer, "RoleStore"); const roleStore: Contract = await RoleStore.deploy(); await roleStore.deployed(); console.log("RoleStore deployed to:", roleStore.address); contracts.roleStore = roleStore.address; - await delay(deployDelay); + await delay(txDelay); // DataStore - const DataStore: ContractFactory = await getFactory("DataStore"); + const DataStore: ContractFactory = await getFactory(deployer, "DataStore"); const dataStore: Contract = await DataStore.deploy(roleStore.address); await dataStore.deployed(); console.log("DataStore deployed to:", dataStore.address); contracts.dataStore = dataStore.address; - await delay(deployDelay); + await delay(txDelay); // EventEmitter - const EventEmitter: ContractFactory = await getFactory("EventEmitter"); + const EventEmitter: ContractFactory = await getFactory(deployer, "EventEmitter"); const eventEmitter: Contract = await EventEmitter.deploy(roleStore.address); await eventEmitter.deployed(); console.log("EventEmitter deployed to:", eventEmitter.address); contracts.eventEmitter = eventEmitter.address; - await delay(deployDelay); + await delay(txDelay); // Grant roles early for configuration - const RoleStoreContract = await getFactory("RoleStore"); - const roleStoreForRoles = RoleStoreContract.attach(contracts.roleStore); - await roleStoreForRoles.grantRole(deployerAddress, hashString("CONTROLLER")); - await delay(deployDelay); - await roleStoreForRoles.grantRole(deployerAddress, hashString("FEE_DISTRIBUTION_KEEPER")); - await delay(deployDelay); - await roleStoreForRoles.grantRole( - "0x6A3a4fAbCf80569392D9F4e46fBE2aF46159a907", - hashString("FEE_DISTRIBUTION_KEEPER") - ); - await delay(deployDelay); - await roleStoreForRoles.grantRole(deployerAddress, hashString("CONFIG_KEEPER")); - await delay(deployDelay); + await (await roleStore.grantRole(deployerAddress, hashString("CONTROLLER"))).wait(); + await delay(txDelay); + await (await roleStore.grantRole(deployerAddress, hashString("TIMELOCK_ADMIN"))).wait(); + await delay(txDelay); + await (await roleStore.grantRole(deployerAddress, hashString("FEE_DISTRIBUTION_KEEPER"))).wait(); + await delay(txDelay); + await (await roleStore.grantRole(deployerAddress, hashString("CONFIG_KEEPER"))).wait(); + await delay(txDelay); saveCheckpoint(1, contracts); } @@ -198,21 +192,21 @@ async function deployContracts(): Promise { if (!checkpoint || checkpoint.step < 2) { console.log("\n2. Deploying libraries..."); - const MarketEventUtils: ContractFactory = await getFactory("MarketEventUtils"); + const MarketEventUtils: ContractFactory = await getFactory(deployer, "MarketEventUtils"); const marketEventUtils: Contract = await MarketEventUtils.deploy(); await marketEventUtils.deployed(); console.log("MarketEventUtils deployed to:", marketEventUtils.address); contracts.marketEventUtils = marketEventUtils.address; - await delay(deployDelay); + await delay(txDelay); - const MarketStoreUtils: ContractFactory = await getFactory("MarketStoreUtils"); + const MarketStoreUtils: ContractFactory = await getFactory(deployer, "MarketStoreUtils"); const marketStoreUtils: Contract = await MarketStoreUtils.deploy(); await marketStoreUtils.deployed(); console.log("MarketStoreUtils deployed to:", marketStoreUtils.address); contracts.marketStoreUtils = marketStoreUtils.address; - await delay(deployDelay); + await delay(txDelay); - const MarketUtils: ContractFactory = await getFactory("MarketUtils", { + const MarketUtils: ContractFactory = await getFactory(deployer, "MarketUtils", { libraries: { MarketEventUtils: contracts.marketEventUtils, MarketStoreUtils: contracts.marketStoreUtils, @@ -222,9 +216,9 @@ async function deployContracts(): Promise { await marketUtils.deployed(); console.log("MarketUtils deployed to:", marketUtils.address); contracts.marketUtils = marketUtils.address; - await delay(deployDelay); + await delay(txDelay); - const ConfigUtils: ContractFactory = await getFactory("ConfigUtils", { + const ConfigUtils: ContractFactory = await getFactory(deployer, "ConfigUtils", { libraries: { MarketUtils: contracts.marketUtils, }, @@ -233,24 +227,24 @@ async function deployContracts(): Promise { await configUtils.deployed(); console.log("ConfigUtils deployed to:", configUtils.address); contracts.configUtils = configUtils.address; - await delay(deployDelay); + await delay(txDelay); - const FeeDistributorUtils: ContractFactory = await getFactory("FeeDistributorUtils"); + const FeeDistributorUtils: ContractFactory = await getFactory(deployer, "FeeDistributorUtils"); const feeDistributorUtils: Contract = await FeeDistributorUtils.deploy(); await feeDistributorUtils.deployed(); console.log("FeeDistributorUtils deployed to:", feeDistributorUtils.address); contracts.feeDistributorUtils = feeDistributorUtils.address; - await delay(deployDelay); + await delay(txDelay); - const ClaimUtils: ContractFactory = await getFactory("ClaimUtils"); + const ClaimUtils: ContractFactory = await getFactory(deployer, "ClaimUtils"); const claimUtils: Contract = await ClaimUtils.deploy(); await claimUtils.deployed(); console.log("ClaimUtils deployed to:", claimUtils.address); contracts.claimUtils = claimUtils.address; - await delay(deployDelay); + await delay(txDelay); // Config - const Config: ContractFactory = await getFactory("Config", { + const Config: ContractFactory = await getFactory(deployer, "Config", { libraries: { ConfigUtils: contracts.configUtils, }, @@ -263,14 +257,13 @@ async function deployContracts(): Promise { await configContract.deployed(); console.log("Config deployed to:", configContract.address); contracts.config = configContract.address; - await delay(deployDelay); + await delay(txDelay); // Grant role to config - const RoleStoreContract = await getFactory("RoleStore"); - const roleStoreForConfig = RoleStoreContract.attach(contracts.roleStore); - await delay(deployDelay); - await roleStoreForConfig.grantRole(contracts.config, hashString("CONTROLLER")); - await delay(deployDelay); + const RoleStoreContract = await getFactory(deployer, "RoleStore"); + const roleStore = RoleStoreContract.attach(contracts.roleStore); + await (await roleStore.grantRole(contracts.config, hashString("CONTROLLER"))).wait(); + await delay(txDelay); saveCheckpoint(2, contracts); } @@ -279,25 +272,25 @@ async function deployContracts(): Promise { if (!checkpoint || checkpoint.step < 3) { console.log("\n3. Deploying tokens..."); - const MintableToken: ContractFactory = await getFactory("MintableToken"); + const MintableToken: ContractFactory = await getFactory(deployer, "MintableToken"); const gmx: Contract = await MintableToken.deploy("GMX", "GMX", 18); await gmx.deployed(); console.log("GMX deployed to:", gmx.address); contracts.gmx = gmx.address; - await delay(deployDelay); + await delay(txDelay); const esGmx: Contract = await MintableToken.deploy("esGMX", "esGMX", 18); await esGmx.deployed(); console.log("esGMX deployed to:", esGmx.address); contracts.esGmx = esGmx.address; - await delay(deployDelay); + await delay(txDelay); const wnt: Contract = await MintableToken.deploy("WETH", "WETH", 18); await wnt.deployed(); console.log("WNT deployed to:", wnt.address); contracts.wnt = wnt.address; - await delay(deployDelay); + await delay(txDelay); saveCheckpoint(3, contracts); } @@ -306,7 +299,7 @@ async function deployContracts(): Promise { if (!checkpoint || checkpoint.step < 4) { console.log("\n4. Deploying Oracle components..."); - const Oracle: ContractFactory = await getFactory("Oracle"); + const Oracle: ContractFactory = await getFactory(deployer, "Oracle"); const oracle: Contract = await Oracle.deploy( contracts.roleStore, contracts.dataStore, @@ -316,42 +309,41 @@ async function deployContracts(): Promise { await oracle.deployed(); console.log("Oracle deployed to:", oracle.address); contracts.oracle = oracle.address; - await delay(deployDelay); + await delay(txDelay); - const ChainlinkPriceFeedProvider: ContractFactory = await getFactory("ChainlinkPriceFeedProvider"); + const ChainlinkPriceFeedProvider: ContractFactory = await getFactory(deployer, "ChainlinkPriceFeedProvider"); const chainlinkPriceFeedProvider: Contract = await ChainlinkPriceFeedProvider.deploy(contracts.dataStore); await chainlinkPriceFeedProvider.deployed(); console.log("ChainlinkPriceFeedProvider deployed to:", chainlinkPriceFeedProvider.address); contracts.chainlinkPriceFeedProvider = chainlinkPriceFeedProvider.address; - await delay(deployDelay); + await delay(txDelay); // Grant role to price feed provider and oracle - const RoleStoreContract = await getFactory("RoleStore"); - const roleStoreForOracle = RoleStoreContract.attach(contracts.roleStore); - await delay(deployDelay); - await roleStoreForOracle.grantRole(contracts.chainlinkPriceFeedProvider, hashString("CONTROLLER")); - await delay(deployDelay); - await roleStoreForOracle.grantRole(contracts.oracle, hashString("CONTROLLER")); - await delay(deployDelay); + const RoleStoreContract = await getFactory(deployer, "RoleStore"); + const roleStore = RoleStoreContract.attach(contracts.roleStore); + await (await roleStore.grantRole(contracts.chainlinkPriceFeedProvider, hashString("CONTROLLER"))).wait(); + await delay(txDelay); + await (await roleStore.grantRole(contracts.oracle, hashString("CONTROLLER"))).wait(); + await delay(txDelay); // Deploy mock price feeds - const WETHPriceFeed: ContractFactory = await getFactory("MockPriceFeed"); + const WETHPriceFeed: ContractFactory = await getFactory(deployer, "MockPriceFeed"); const wethPriceFeed: Contract = await WETHPriceFeed.deploy(); await wethPriceFeed.deployed(); - await delay(deployDelay); - await wethPriceFeed.setAnswer(expandDecimals(5_000, 8)); // $5000 + await delay(txDelay); + await (await wethPriceFeed.setAnswer(expandDecimals(5_000, 8))).wait(); // $5000 console.log("WETH Price Feed deployed to:", wethPriceFeed.address); contracts.wethPriceFeed = wethPriceFeed.address; - await delay(deployDelay); + await delay(txDelay); - const GMXPriceFeed: ContractFactory = await getFactory("MockPriceFeed"); + const GMXPriceFeed: ContractFactory = await getFactory(deployer, "MockPriceFeed"); const gmxPriceFeed: Contract = await GMXPriceFeed.deploy(); await gmxPriceFeed.deployed(); - await delay(deployDelay); - await gmxPriceFeed.setAnswer(expandDecimals(20, 8)); // $20 + await delay(txDelay); + await (await gmxPriceFeed.setAnswer(expandDecimals(20, 8))).wait(); // $20 console.log("GMX Price Feed deployed to:", gmxPriceFeed.address); contracts.gmxPriceFeed = gmxPriceFeed.address; - await delay(deployDelay); + await delay(txDelay); saveCheckpoint(4, contracts); } @@ -367,7 +359,7 @@ async function deployContracts(): Promise { if (network === "localhost") { // Deploy two separate mock endpoints for localhost to avoid reentrancy issues - const MockEndpointV2: ContractFactory = await getFactory("MockEndpointV2"); + const MockEndpointV2: ContractFactory = await getFactory(deployer, "MockEndpointV2"); // First endpoint for MultichainReader mockEndpointMultichainReader = await MockEndpointV2.deploy(config.eid); @@ -405,33 +397,33 @@ async function deployContracts(): Promise { if (!checkpoint || checkpoint.step < 6) { console.log("\n6. Deploying mock contracts..."); - const MockVaultV1: ContractFactory = await getFactory("MockVaultV1"); + const MockVaultV1: ContractFactory = await getFactory(deployer, "MockVaultV1"); const mockVaultV1: Contract = await MockVaultV1.deploy(deployerAddress); await mockVaultV1.deployed(); console.log("MockVaultV1 deployed to:", mockVaultV1.address); contracts.mockVaultV1 = mockVaultV1.address; - await delay(deployDelay); + await delay(txDelay); - const MockRewardDistributorV1: ContractFactory = await getFactory("MockRewardDistributorV1"); + const MockRewardDistributorV1: ContractFactory = await getFactory(deployer, "MockRewardDistributorV1"); const mockRewardDistributor: Contract = await MockRewardDistributorV1.deploy(); await mockRewardDistributor.deployed(); console.log("MockRewardDistributorV1 deployed to:", mockRewardDistributor.address); contracts.mockRewardDistributor = mockRewardDistributor.address; - await delay(deployDelay); + await delay(txDelay); - const MockRewardTrackerV1: ContractFactory = await getFactory("MockRewardTrackerV1"); + const MockRewardTrackerV1: ContractFactory = await getFactory(deployer, "MockRewardTrackerV1"); const mockExtendedGmxTracker: Contract = await MockRewardTrackerV1.deploy(contracts.mockRewardDistributor); await mockExtendedGmxTracker.deployed(); console.log("MockRewardTrackerV1 deployed to:", mockExtendedGmxTracker.address); contracts.mockExtendedGmxTracker = mockExtendedGmxTracker.address; - await delay(deployDelay); + await delay(txDelay); - const MockVesterV1: ContractFactory = await getFactory("MockVesterV1"); + const MockVesterV1: ContractFactory = await getFactory(deployer, "MockVesterV1"); const mockVester: Contract = await MockVesterV1.deploy([deployerAddress], [expandDecimals(1, 18)]); await mockVester.deployed(); console.log("MockVesterV1 deployed to:", mockVester.address); contracts.mockVester = mockVester.address; - await delay(deployDelay); + await delay(txDelay); saveCheckpoint(6, contracts); } @@ -440,7 +432,7 @@ async function deployContracts(): Promise { if (!checkpoint || checkpoint.step < 7) { console.log("\n7. Deploying fee contracts..."); - const FeeHandler: ContractFactory = await getFactory("FeeHandler", { + const FeeHandler: ContractFactory = await getFactory(deployer, "FeeHandler", { libraries: { MarketUtils: contracts.marketUtils, }, @@ -456,24 +448,24 @@ async function deployContracts(): Promise { await feeHandler.deployed(); console.log("FeeHandler deployed to:", feeHandler.address); contracts.feeHandler = feeHandler.address; - await delay(deployDelay); + await delay(txDelay); - const FeeDistributorVault: ContractFactory = await getFactory("FeeDistributorVault"); + const FeeDistributorVault: ContractFactory = await getFactory(deployer, "FeeDistributorVault"); const feeDistributorVault: Contract = await FeeDistributorVault.deploy(contracts.roleStore, contracts.dataStore); await feeDistributorVault.deployed(); console.log("FeeDistributorVault deployed to:", feeDistributorVault.address); contracts.feeDistributorVault = feeDistributorVault.address; - await delay(deployDelay); + await delay(txDelay); - const ClaimVault: ContractFactory = await getFactory("ClaimVault"); + const ClaimVault: ContractFactory = await getFactory(deployer, "ClaimVault"); const claimVault: Contract = await ClaimVault.deploy(contracts.roleStore, contracts.dataStore); await claimVault.deployed(); console.log("ClaimVault deployed to:", claimVault.address); contracts.claimVault = claimVault.address; - await delay(deployDelay); + await delay(txDelay); // Deploy MultichainReader with its dedicated endpoint - const MultichainReader: ContractFactory = await getFactory("MultichainReader"); + const MultichainReader: ContractFactory = await getFactory(deployer, "MultichainReader"); const multichainReader: Contract = await MultichainReader.deploy( contracts.roleStore, contracts.dataStore, @@ -483,20 +475,19 @@ async function deployContracts(): Promise { await multichainReader.deployed(); console.log("MultichainReader deployed to:", multichainReader.address); contracts.multichainReader = multichainReader.address; - await delay(deployDelay); + await delay(txDelay); - const MockGMXAdapter: ContractFactory = await getFactory("MockGMX_Adapter"); + const MockGMXAdapter: ContractFactory = await getFactory(deployer, "MockGMX_MintBurnAdapter"); const gmxAdapter = await MockGMXAdapter.deploy( [{ dstEid: config.counterEid, limit: expandDecimals(1000000, 18), window: 60 }], contracts.gmx, - contracts.gmx, contracts.endpointForGmxAdapter, deployerAddress ); await gmxAdapter.deployed(); - console.log("MockGMX_Adapter deployed to:", gmxAdapter.address); + console.log("MockGMX_MintBurnAdapter deployed to:", gmxAdapter.address); contracts.gmxAdapter = gmxAdapter.address; - await delay(deployDelay); + await delay(txDelay); saveCheckpoint(7, contracts); } @@ -505,7 +496,7 @@ async function deployContracts(): Promise { if (!checkpoint || checkpoint.step < 8) { console.log("\n8. Deploying FeeDistributor..."); - const FeeDistributor: ContractFactory = await getFactory("FeeDistributor", { + const FeeDistributor: ContractFactory = await getFactory(deployer, "FeeDistributor", { libraries: { FeeDistributorUtils: contracts.feeDistributorUtils, ClaimUtils: contracts.claimUtils, @@ -526,7 +517,7 @@ async function deployContracts(): Promise { await feeDistributor.deployed(); console.log("FeeDistributor deployed to:", feeDistributor.address); contracts.feeDistributor = feeDistributor.address; - await delay(deployDelay); + await delay(txDelay); saveCheckpoint(8, contracts); } @@ -535,22 +526,20 @@ async function deployContracts(): Promise { if (!checkpoint || checkpoint.step < 9) { console.log("\n9. Granting remaining roles..."); - const RoleStoreContract = await getFactory("RoleStore"); - const roleStoreFinal = RoleStoreContract.attach(contracts.roleStore); - await delay(deployDelay); - - await roleStoreFinal.grantRole(contracts.feeDistributor, hashString("CONTROLLER")); - await delay(deployDelay); - await roleStoreFinal.grantRole(contracts.feeDistributor, hashString("FEE_KEEPER")); - await delay(deployDelay); - await roleStoreFinal.grantRole(contracts.multichainReader, hashString("CONTROLLER")); - await delay(deployDelay); - await roleStoreFinal.grantRole(contracts.multichainReader, hashString("MULTICHAIN_READER")); - await delay(deployDelay); - await roleStoreFinal.grantRole(contracts.feeHandler, hashString("CONTROLLER")); - await delay(deployDelay); - await roleStoreFinal.grantRole(contracts.feeDistributorVault, hashString("CONTROLLER")); - await delay(deployDelay); + const RoleStoreContract = await getFactory(deployer, "RoleStore"); + const roleStore = RoleStoreContract.attach(contracts.roleStore); + await (await roleStore.grantRole(contracts.feeDistributor, hashString("CONTROLLER"))).wait(); + await delay(txDelay); + await (await roleStore.grantRole(contracts.feeDistributor, hashString("FEE_KEEPER"))).wait(); + await delay(txDelay); + await (await roleStore.grantRole(contracts.multichainReader, hashString("CONTROLLER"))).wait(); + await delay(txDelay); + await (await roleStore.grantRole(contracts.multichainReader, hashString("MULTICHAIN_READER"))).wait(); + await delay(txDelay); + await (await roleStore.grantRole(contracts.feeHandler, hashString("CONTROLLER"))).wait(); + await delay(txDelay); + await (await roleStore.grantRole(contracts.feeDistributorVault, hashString("CONTROLLER"))).wait(); + await delay(txDelay); console.log("Roles granted successfully"); diff --git a/scripts/processLzReceiveLocalhost.ts b/scripts/processLzReceiveLocalhost.ts index e3aa95966..aaa720e97 100644 --- a/scripts/processLzReceiveLocalhost.ts +++ b/scripts/processLzReceiveLocalhost.ts @@ -54,7 +54,7 @@ async function main() { const mockEndpointV2C = await deployContract("MockEndpointV2", [eidC]); const gmxA = await deployContract("MintableToken", ["GMX", "GMX", 18]); const gmxC = await deployContract("MintableToken", ["GMX", "GMX", 18]); - const mockGmxAdapterA = await deployContract("MockGMX_Adapter", [ + const mockGmxAdapterA = await deployContract("MockGMX_MintBurnAdapter", [ [ { dstEid: eidB, limit: expandDecimals(1000000, 18), window: 60 }, { dstEid: eidC, limit: expandDecimals(1000000, 18), window: 60 }, @@ -64,7 +64,7 @@ async function main() { mockEndpointV2A.address, accounts[0].address, ]); - const mockGmxAdapterB = await deployContract("MockGMX_Adapter", [ + const mockGmxAdapterB = await deployContract("MockGMX_LockboxAdapter", [ [ { dstEid: eidA, limit: expandDecimals(1000000, 18), window: 60 }, { dstEid: eidC, limit: expandDecimals(1000000, 18), window: 60 }, @@ -74,7 +74,7 @@ async function main() { mockEndpointV2B.address, accounts[0].address, ]); - const mockGmxAdapterC = await deployContract("MockGMX_Adapter", [ + const mockGmxAdapterC = await deployContract("MockGMX_MintBurnAdapter", [ [ { dstEid: eidA, limit: expandDecimals(1000000, 18), window: 60 }, { dstEid: eidB, limit: expandDecimals(1000000, 18), window: 60 }, @@ -278,6 +278,7 @@ async function main() { await gmx.mint(feeHandler.address, expandDecimals(40_000, 18)); await gmx.mint(accounts[0].address, expandDecimals(170_000, 18)); + await gmx.approve(mockGmxAdapterB.address, expandDecimals(50_000, 18)); let sendParam = { dstEid: eidA, to: addressToBytes32(accounts[1].address), diff --git a/scripts/sendPayments.ts b/scripts/sendPayments.ts new file mode 100644 index 000000000..ddd97962f --- /dev/null +++ b/scripts/sendPayments.ts @@ -0,0 +1,40 @@ +import hre from "hardhat"; +import { hashString } from "../utils/hash"; + +async function main() { + console.log("Running script to call ContributorHandler.sendPayments()"); + + try { + const [signer] = await hre.ethers.getSigners(); + if (!signer) { + throw new Error("No signer found"); + } + const signerAddress = await signer.getAddress(); + const contributorHandler = await hre.ethers.getContract("ContributorHandler", signer); + const roleStore = await hre.ethers.getContract("RoleStore"); + + const CONTRIBUTOR_DISTRIBUTOR = "CONTRIBUTOR_DISTRIBUTOR"; + const hasRole = await roleStore.hasRole(signerAddress, hashString(CONTRIBUTOR_DISTRIBUTOR)); + if (!hasRole) { + throw new Error( + `Address: ${signerAddress} must be granted the ${CONTRIBUTOR_DISTRIBUTOR} role to execute ContributorHandler.sendPayments()` + ); + } + + console.log("Executing sendPayments transaction..."); + + const tx = await contributorHandler.sendPayments(); + + console.log(`Transaction hash: ${tx.hash}`); + console.log("Waiting for transaction confirmation..."); + + const receipt = await tx.wait(); + console.log(`Transaction confirmed in block ${receipt.blockNumber}`); + console.log("sendPayments completed successfully!"); + } catch (error) { + console.log("Error during sendPayments:", error); + process.exit(1); + } +} + +main().catch(console.error); diff --git a/test/fee/FeeDistributor.ts b/test/fee/FeeDistributor.ts index 7d46c596a..4104146b9 100644 --- a/test/fee/FeeDistributor.ts +++ b/test/fee/FeeDistributor.ts @@ -160,36 +160,33 @@ describe("FeeDistributor", function () { mockEndpointV2C = await deployContract("MockEndpointV2", [eidC]); gmxA = await deployContract("MintableToken", ["GMX", "GMX", 18]); gmxC = await deployContract("MintableToken", ["GMX", "GMX", 18]); - mockGmxAdapterA = await deployContract("MockGMX_Adapter", [ + mockGmxAdapterA = await deployContract("MockGMX_MintBurnAdapter", [ [ { dstEid: eidB, limit: expandDecimals(1000000, 18), window: 60 }, { dstEid: eidC, limit: expandDecimals(1000000, 18), window: 60 }, { dstEid: eidD, limit: expandDecimals(1000000, 18), window: 60 }, ], gmxA.address, - gmxA.address, mockEndpointV2A.address, wallet.address, ]); - mockGmxAdapterB = await deployContract("MockGMX_Adapter", [ + mockGmxAdapterB = await deployContract("MockGMX_LockboxAdapter", [ [ { dstEid: eidA, limit: expandDecimals(1000000, 18), window: 60 }, { dstEid: eidC, limit: expandDecimals(1000000, 18), window: 60 }, { dstEid: eidD, limit: expandDecimals(1000000, 18), window: 60 }, ], gmx.address, - gmx.address, mockEndpointV2B.address, wallet.address, ]); - mockGmxAdapterC = await deployContract("MockGMX_Adapter", [ + mockGmxAdapterC = await deployContract("MockGMX_MintBurnAdapter", [ [ { dstEid: eidA, limit: expandDecimals(1000000, 18), window: 60 }, { dstEid: eidB, limit: expandDecimals(1000000, 18), window: 60 }, { dstEid: eidD, limit: expandDecimals(1000000, 18), window: 60 }, ], gmxC.address, - gmxC.address, mockEndpointV2C.address, wallet.address, ]); @@ -435,6 +432,7 @@ describe("FeeDistributor", function () { await gmx.mint(feeHandler.address, expandDecimals(10_000, 18)); await gmx.mint(wallet.address, expandDecimals(170_000, 18)); + await gmx.approve(mockGmxAdapterB.address, expandDecimals(130_000, 18)); let sendParam = { dstEid: eidA, to: addressToBytes32(user0.address), @@ -544,6 +542,7 @@ describe("FeeDistributor", function () { await gmx.mint(feeHandler.address, expandDecimals(40_000, 18)); await gmx.mint(wallet.address, expandDecimals(170_000, 18)); + await gmx.approve(mockGmxAdapterB.address, expandDecimals(50_000, 18)); let sendParam = { dstEid: eidA, to: addressToBytes32(user0.address), @@ -598,8 +597,8 @@ describe("FeeDistributor", function () { const distributeTimestamp = await dataStore.getUint(keys.FEE_DISTRIBUTOR_READ_RESPONSE_TIMESTAMP); - const feeDistributionInitiatedEventData = parseLogs(fixture, receipt)[14].parsedEventData; - const feeDistributionDataReceivedEventData = parseLogs(fixture, receipt)[11].parsedEventData; + const feeDistributionInitiatedEventData = parseLogs(fixture, receipt)[18].parsedEventData; + const feeDistributionDataReceivedEventData = parseLogs(fixture, receipt)[15].parsedEventData; const feeAmountGmxA = await dataStore.getUint(keys.feeDistributorFeeAmountGmxKey(chainIdA)); const feeAmountGmxB = await dataStore.getUint(keys.feeDistributorFeeAmountGmxKey(chainIdB)); @@ -669,6 +668,7 @@ describe("FeeDistributor", function () { await gmx.mint(feeHandler.address, expandDecimals(10_000, 18)); await gmx.mint(wallet.address, expandDecimals(170_000, 18)); + await gmx.approve(mockGmxAdapterB.address, expandDecimals(130_000, 18)); let sendParam = { dstEid: eidA, to: addressToBytes32(user0.address), @@ -932,6 +932,7 @@ describe("FeeDistributor", function () { await gmx.mint(feeHandler.address, expandDecimals(40_000, 18)); await gmx.mint(wallet.address, expandDecimals(170_000, 18)); + await gmx.approve(mockGmxAdapterB.address, expandDecimals(50_000, 18)); let sendParam = { dstEid: eidA, to: addressToBytes32(user0.address), @@ -988,8 +989,8 @@ describe("FeeDistributor", function () { const distributeTimestamp = await dataStore.getUint(keys.FEE_DISTRIBUTOR_READ_RESPONSE_TIMESTAMP); - const feeDistributionInitiatedEventData = parseLogs(fixture, receipt)[14].parsedEventData; - const feeDistributionDataReceivedEventData = parseLogs(fixture, receipt)[11].parsedEventData; + const feeDistributionInitiatedEventData = parseLogs(fixture, receipt)[18].parsedEventData; + const feeDistributionDataReceivedEventData = parseLogs(fixture, receipt)[15].parsedEventData; const feeAmountGmxA = await dataStore.getUint(keys.feeDistributorFeeAmountGmxKey(chainIdA)); const feeAmountGmxB = await dataStore.getUint(keys.feeDistributorFeeAmountGmxKey(chainIdB)); @@ -1299,14 +1300,13 @@ describe("FeeDistributor", function () { const mockEndpointV2DMultichain = await deployContract("MockEndpointV2", [eidD]); const mockEndpointV2D = await deployContract("MockEndpointV2", [eidD]); const gmxD = await deployContract("MintableToken", ["GMX", "GMX", 18]); - const mockGmxAdapterD = await deployContract("MockGMX_Adapter", [ + const mockGmxAdapterD = await deployContract("MockGMX_MintBurnAdapter", [ [ { dstEid: eidA, limit: expandDecimals(1000000, 18), window: 60 }, { dstEid: eidB, limit: expandDecimals(1000000, 18), window: 60 }, { dstEid: eidC, limit: expandDecimals(1000000, 18), window: 60 }, ], gmxD.address, - gmxD.address, mockEndpointV2D.address, wallet.address, ]); @@ -1638,6 +1638,7 @@ describe("FeeDistributor", function () { await dataStoreD.setUint(keys.withdrawableBuybackTokenAmountKey(gmxD.address), expandDecimals(50_000, 18)); await gmx.mint(wallet.address, expandDecimals(290_000, 18)); + await gmx.approve(mockGmxAdapterB.address, expandDecimals(170_000, 18)); let sendParam = { dstEid: eidA, to: addressToBytes32(user0.address), @@ -1737,8 +1738,8 @@ describe("FeeDistributor", function () { const distributeTimestamp = await dataStore.getUint(keys.FEE_DISTRIBUTOR_READ_RESPONSE_TIMESTAMP); const distributeTimestampD = await dataStoreD.getUint(keys.FEE_DISTRIBUTOR_READ_RESPONSE_TIMESTAMP); - const feeDistributionInitiatedEventData = parseLogs(fixture, receipt)[14].parsedEventData; - const feeDistributionDataReceivedEventData = parseLogs(fixture, receipt)[11].parsedEventData; + const feeDistributionInitiatedEventData = parseLogs(fixture, receipt)[18].parsedEventData; + const feeDistributionDataReceivedEventData = parseLogs(fixture, receipt)[15].parsedEventData; const feeDistributionInitiatedEventDataD = parseLogs(fixture, receiptD)[9].parsedEventData; const feeDistributionDataReceivedEventDataD = parseLogs(fixture, receiptD)[6].parsedEventData;