From 6a38abb7ed9c74afa9b8f63bd723d9d690ee6046 Mon Sep 17 00:00:00 2001 From: adu Date: Tue, 25 Jun 2024 09:40:31 +0800 Subject: [PATCH 01/19] refactor ExocoreGateway._registerClientChain --- src/core/ExocoreGateway.sol | 37 ++++++++++++++++++--- src/interfaces/precompiles/IAssets.sol | 45 +++++++++++++++++++------- 2 files changed, 66 insertions(+), 16 deletions(-) diff --git a/src/core/ExocoreGateway.sol b/src/core/ExocoreGateway.sol index 10234e03..453ef5cb 100644 --- a/src/core/ExocoreGateway.sol +++ b/src/core/ExocoreGateway.sol @@ -118,19 +118,36 @@ contract ExocoreGateway is * register the `cientChainId` to Exocore native module if the peer address is first time being set. * @param clientChainId The endpoint ID for client chain. * @param clientChainGateway The contract address to be associated with the corresponding endpoint. + * @param addressLength The bytes length of address type on that client chain + * @param name The name of client chain + * @param metaInfo The arbitrary metadata for client chain + * @param signatureType The cryptographic signature type that client chain supports * * @dev Only the owner/admin of the OApp can call this function. * @dev Indicates that the peer is trusted to send LayerZero messages to this OApp. * @dev Peer is a bytes32 to accommodate non-evm chains. */ - function setPeer(uint32 clientChainId, bytes32 clientChainGateway) + function setPeer( + uint32 clientChainId, + bytes32 clientChainGateway, + uint32 addressLength, + string memory name, + string memory metaInfo, + string memory signatureType + ) public override(IOAppCore, OAppCoreUpgradeable) onlyOwner whenNotPaused { _validatePeer(clientChainId, clientChainGateway); - _registerClientChain(clientChainId); + _registerClientChain( + clientChainId, + uint32 addressLength, + string memory name, + string memory metaInfo, + string memory signatureType + ); super.setPeer(clientChainId, clientChainGateway); } @@ -139,9 +156,21 @@ contract ExocoreGateway is require(clientChainGateway != bytes32(0), "ExocoreGateway: client chain gateway cannot be empty"); } - function _registerClientChain(uint32 clientChainId) internal { + function _registerClientChain( + uint32 clientChainID, + uint32 addressLength, + string memory name, + string memory metaInfo, + string memory signatureType + ) internal { if (peers[clientChainId] == bytes32(0)) { - bool success = ASSETS_CONTRACT.registerClientChain(clientChainId); + bool success = ASSETS_CONTRACT.registerClientChain( + uint32 clientChainID, + uint32 addressLength, + string memory name, + string memory metaInfo, + string memory signatureType + ); if (!success) { revert RegisterClientChainToExocoreFailed(clientChainId); } diff --git a/src/interfaces/precompiles/IAssets.sol b/src/interfaces/precompiles/IAssets.sol index 0b1f04d3..dbaacfe3 100644 --- a/src/interfaces/precompiles/IAssets.sol +++ b/src/interfaces/precompiles/IAssets.sol @@ -16,23 +16,30 @@ interface IAssets { /// @dev deposit the client chain assets for the staker, /// that will change the state in deposit module /// Note that this address cannot be a module account. - /// @param clientChainLzID The LzID of client chain + /// @param clientChainID is the layerZero chainID if it is supported. + // It might be allocated by Exocore when the client chain isn't supported + // by layerZero /// @param assetsAddress The client chain asset address /// @param stakerAddress The staker address /// @param opAmount The amount to deposit - function depositTo(uint32 clientChainLzID, bytes memory assetsAddress, bytes memory stakerAddress, uint256 opAmount) - external - returns (bool success, uint256 latestAssetState); + function depositTo( + uint32 clientChainID, + bytes memory assetsAddress, + bytes memory stakerAddress, + uint256 opAmount) external + returns (bool success, uint256 latestAssetState); /// TRANSACTIONS /// @dev withdraw To the staker, that will change the state in withdraw module /// Note that this address cannot be a module account. - /// @param clientChainLzID The LzID of client chain + /// @param clientChainID is the layerZero chainID if it is supported. + // It might be allocated by Exocore when the client chain isn't supported + // by layerZero /// @param assetsAddress The client chain asset Address /// @param withdrawAddress The withdraw address /// @param opAmount The withdraw amount function withdrawPrincipal( - uint32 clientChainLzID, + uint32 clientChainID, bytes memory assetsAddress, bytes memory withdrawAddress, uint256 opAmount @@ -45,13 +52,27 @@ interface IAssets { /// TRANSACTIONS /// @dev register some client chain to allow token registration from that chain, staking /// from that chain, and other operations from that chain. - /// @param clientChainLzID The LzID of client chain - function registerClientChain(uint32 clientChainLzID) external returns (bool success); + /// @param clientChainID is the layerZero chainID if it is supported. + // It might be allocated by Exocore when the client chain isn't supported + // by layerZero + function registerClientChain( + uint32 clientChainID, + uint32 addressLength, + string memory name, + string memory metaInfo, + string memory signatureType + ) external returns (bool success); /// TRANSACTIONS /// @dev register unwhitelisted token addresses to exocore - /// @param clientChainLzID The LzID of client chain + /// @param clientChainID is the layerZero chainID if it is supported. + // It might be allocated by Exocore when the client chain isn't supported + // by layerZero /// @param tokens The token addresses that would be registered to exocore - function registerTokens(uint32 clientChainLzID, bytes[] memory tokens) external returns (bool success); - -} + function registerTokens( + uint32 clientChainID, + bytes[] memory tokens, + uint8[] memory decimals, + uint256[] memory tvlLimit + ) external returns (bool success); +} \ No newline at end of file From 1abe497199b5fce6de505248033d59140913e3bd Mon Sep 17 00:00:00 2001 From: adu Date: Tue, 25 Jun 2024 10:29:22 +0800 Subject: [PATCH 02/19] refactor ClientChainGateway.addWhitelistTokens and ExocoreGateway.requestRegisterTokens --- src/core/ClientChainGateway.sol | 23 +++++++++++++++++++---- src/core/ExocoreGateway.sol | 18 +++++++++++++++--- src/storage/ClientChainGatewayStorage.sol | 1 + src/storage/ExocoreGatewayStorage.sol | 2 ++ 4 files changed, 37 insertions(+), 7 deletions(-) diff --git a/src/core/ClientChainGateway.sol b/src/core/ClientChainGateway.sol index bf39d044..fc8025b8 100644 --- a/src/core/ClientChainGateway.sol +++ b/src/core/ClientChainGateway.sol @@ -10,6 +10,7 @@ import {ClientGatewayLzReceiver} from "./ClientGatewayLzReceiver.sol"; import {LSTRestakingController} from "./LSTRestakingController.sol"; import {NativeRestakingController} from "./NativeRestakingController.sol"; +import {ERC20} from "@openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; import {IOAppCore} from "@layerzero-v2/oapp/contracts/oapp/interfaces/IOAppCore.sol"; import {OptionsBuilder} from "@layerzero-v2/oapp/contracts/oapp/libs/OptionsBuilder.sol"; import {OwnableUpgradeable} from "@openzeppelin-upgradeable/contracts/access/OwnableUpgradeable.sol"; @@ -124,22 +125,36 @@ contract ClientChainGateway is _addWhitelistTokens(tokens); } - function _addWhitelistTokens(address[] calldata tokens) internal { + function _addWhitelistTokens(address[] calldata tokens, uint256[] calldata tvlLimits) internal { require(tokens.length <= type(uint8).max, "ClientChainGateway: tokens length should not execeed 255"); + require(tokens.length == tvlLimits.length, "ClientChainGateway: tokens length should match the length of tvl limits"); - bytes memory actionArgs = abi.encodePacked(uint8(tokens.length)); + bytes memory encodedTokens; + bytes memory encodedDecimals; + bytes memory encodedTVLLimits = abi.encodePacked(tvlLimits); for (uint256 i; i < tokens.length; i++) { - address token = tokens[i]; + address token = tokens[i]; require(token != address(0), "ClientChainGateway: zero token address"); require(!isWhitelistedToken[token], "ClientChainGateway: token should not be whitelisted before"); - actionArgs = abi.encodePacked(actionArgs, bytes32(bytes20(token))); + encodedTokens = abi.encodePacked(encodedTokens, bytes32(bytes20(token))); + encodedDecimals = abi.encodePacked(encodedDecimals, _getDecimals(token)); } + actionArgs = abi.encodePacked(tokens.length, encodedTokens, encodedDecimals, encodedTVLLimits); + bytes memory encodedRequest = abi.encode(tokens); _processRequest(Action.REQUEST_REGISTER_TOKENS, actionArgs, encodedRequest); } + function _getDecimals(address token) internal view returns (uint8) { + if (token == VIRTUAL_STAKED_ETH_ADDRESS) { + return ETH_DECIMALS; + } else { + return ERC20(token).decimals(); + } + } + // implementation of ITokenWhitelister function getWhitelistedTokensCount() external view returns (uint256) { return whitelistTokens.length; diff --git a/src/core/ExocoreGateway.sol b/src/core/ExocoreGateway.sol index 453ef5cb..2e0b587a 100644 --- a/src/core/ExocoreGateway.sol +++ b/src/core/ExocoreGateway.sol @@ -208,13 +208,25 @@ contract ExocoreGateway is _validatePayloadLength(payload, expectedLength, Action.REQUEST_DEPOSIT); bytes[] memory tokens = new bytes[](count); + uint8[] memory decimals = new uint8[](count); + uint256[] memory tvlLimits = new uint256[](count); + uint256 start; + uint256 end; for (uint256 i; i < count; i++) { - uint256 start = i * TOKEN_ADDRESS_BYTES_LENTH + 1; - uint256 end = start + TOKEN_ADDRESS_BYTES_LENTH; + start = i * TOKEN_ADDRESS_BYTES_LENTH + 1; + end = start + TOKEN_ADDRESS_BYTES_LENTH; tokens[i] = payload[start:end]; + + start = count * TOKEN_ADDRESS_BYTES_LENTH + i * UINT8_BYTES_LENGTH + 1; + end = start + UINT8_BYTES_LENGTH; + decimals[i] = payload[start:end]; + + start = count * (TOKEN_ADDRESS_BYTES_LENTH + UINT8_BYTES_LENGTH) + i * UINT256_BYTES_LENGTH + 1; + end = start + UINT256_BYTES_LENGTH; + tvlLimits[i] = payload[start:end]; } - try ASSETS_CONTRACT.registerTokens(srcChainId, tokens) returns (bool success) { + try ASSETS_CONTRACT.registerTokens(srcChainId, tokens, decimals, tvlLimits) returns (bool success) { _sendInterchainMsg(srcChainId, Action.RESPOND, abi.encodePacked(lzNonce, success)); } catch { emit ExocorePrecompileError(ASSETS_PRECOMPILE_ADDRESS, lzNonce); diff --git a/src/storage/ClientChainGatewayStorage.sol b/src/storage/ClientChainGatewayStorage.sol index 2a759864..6fb333b8 100644 --- a/src/storage/ClientChainGatewayStorage.sol +++ b/src/storage/ClientChainGatewayStorage.sol @@ -27,6 +27,7 @@ contract ClientChainGatewayStorage is BootstrapStorage { uint128 internal constant DESTINATION_MSG_VALUE = 0; uint256 internal constant GWEI_TO_WEI = 1e9; address internal constant VIRTUAL_STAKED_ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; + uint8 internal constant ETH_DECIMALS = 18; IETHPOSDeposit internal constant ETH_POS = IETHPOSDeposit(0x00000000219ab540356cBB839Cbe05303d7705Fa); uint256[40] private __gap; diff --git a/src/storage/ExocoreGatewayStorage.sol b/src/storage/ExocoreGatewayStorage.sol index 981010d3..daf8b357 100644 --- a/src/storage/ExocoreGatewayStorage.sol +++ b/src/storage/ExocoreGatewayStorage.sol @@ -17,6 +17,8 @@ contract ExocoreGatewayStorage is GatewayStorage { // bytes32 token + bytes32 delegator + bytes(42) operator + uint256 amount uint256 internal constant DEPOSIT_THEN_DELEGATE_REQUEST_LENGTH = DELEGATE_REQUEST_LENGTH; uint256 internal constant TOKEN_ADDRESS_BYTES_LENTH = 32; + uint256 internal constant UINT8_BYTES_LENGTH = 1; + uint256 internal constant UINT256_BYTES_LENGTH = 32; uint128 internal constant DESTINATION_GAS_LIMIT = 500_000; uint128 internal constant DESTINATION_MSG_VALUE = 0; From 80105cff8f24a8af0d6e93b949b08534f7f8347e Mon Sep 17 00:00:00 2001 From: adu Date: Tue, 25 Jun 2024 10:46:51 +0800 Subject: [PATCH 03/19] fix: do not create vault for virtual staked eth address --- src/core/ClientGatewayLzReceiver.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/ClientGatewayLzReceiver.sol b/src/core/ClientGatewayLzReceiver.sol index 354cf569..ced95357 100644 --- a/src/core/ClientGatewayLzReceiver.sol +++ b/src/core/ClientGatewayLzReceiver.sol @@ -197,7 +197,7 @@ abstract contract ClientGatewayLzReceiver is PausableUpgradeable, OAppReceiverUp whitelistTokens.push(token); // deploy the corresponding vault if not deployed before - if (address(tokenToVault[token]) == address(0)) { + if (token != VIRTUAL_STAKED_ETH_ADDRESS && address(tokenToVault[token]) == address(0)) { _deployVault(token); } From f04a7099d836c3169908dc6c013f4dbbfb0c7536 Mon Sep 17 00:00:00 2001 From: adu Date: Tue, 25 Jun 2024 20:21:16 +0800 Subject: [PATCH 04/19] refactor: add whitelist tokens on exocore --- src/core/ClientChainGateway.sol | 37 +---- src/core/ClientGatewayLzReceiver.sol | 32 ++-- src/core/ExocoreGateway.sol | 156 +++++++++++--------- src/interfaces/precompiles/IAssets.sol | 22 +-- src/interfaces/precompiles/IClaimReward.sol | 4 +- src/interfaces/precompiles/IDelegation.sol | 12 +- src/storage/ClientChainGatewayStorage.sol | 2 +- src/storage/ExocoreGatewayStorage.sol | 3 + src/storage/GatewayStorage.sol | 2 +- test/foundry/unit/ClientChainGateway.t.sol | 47 +----- test/foundry/unit/ExocoreGateway.t.sol | 86 +++++++++++ 11 files changed, 225 insertions(+), 178 deletions(-) diff --git a/src/core/ClientChainGateway.sol b/src/core/ClientChainGateway.sol index fc8025b8..dbad8292 100644 --- a/src/core/ClientChainGateway.sol +++ b/src/core/ClientChainGateway.sol @@ -80,6 +80,8 @@ contract ClientChainGateway is this.afterReceiveDepositThenDelegateToResponse.selector; _registeredResponseHooks[Action.REQUEST_REGISTER_TOKENS] = this.afterReceiveRegisterTokensResponse.selector; + _whiteListFunctionSelectors[Action.REQUEST_ADD_WHITELIST_TOKENS] = this.afterReceiveAddWhitelistTokensRequest.selector; + bootstrapped = true; __Ownable_init_unchained(exocoreValidatorSetAddress); @@ -120,39 +122,8 @@ contract ClientChainGateway is _unpause(); } - // implementation of ITokenWhitelister - function addWhitelistTokens(address[] calldata tokens) external payable onlyOwner whenNotPaused nonReentrant { - _addWhitelistTokens(tokens); - } - - function _addWhitelistTokens(address[] calldata tokens, uint256[] calldata tvlLimits) internal { - require(tokens.length <= type(uint8).max, "ClientChainGateway: tokens length should not execeed 255"); - require(tokens.length == tvlLimits.length, "ClientChainGateway: tokens length should match the length of tvl limits"); - - bytes memory encodedTokens; - bytes memory encodedDecimals; - bytes memory encodedTVLLimits = abi.encodePacked(tvlLimits); - for (uint256 i; i < tokens.length; i++) { - address token = tokens[i]; - require(token != address(0), "ClientChainGateway: zero token address"); - require(!isWhitelistedToken[token], "ClientChainGateway: token should not be whitelisted before"); - - encodedTokens = abi.encodePacked(encodedTokens, bytes32(bytes20(token))); - encodedDecimals = abi.encodePacked(encodedDecimals, _getDecimals(token)); - } - - actionArgs = abi.encodePacked(tokens.length, encodedTokens, encodedDecimals, encodedTVLLimits); - - bytes memory encodedRequest = abi.encode(tokens); - _processRequest(Action.REQUEST_REGISTER_TOKENS, actionArgs, encodedRequest); - } - - function _getDecimals(address token) internal view returns (uint8) { - if (token == VIRTUAL_STAKED_ETH_ADDRESS) { - return ETH_DECIMALS; - } else { - return ERC20(token).decimals(); - } + function addWhitelistTokens(address[] calldata tokens) external payable { + revert("this function is not supported for client chain, please register on Exocore"); } // implementation of ITokenWhitelister diff --git a/src/core/ClientGatewayLzReceiver.sol b/src/core/ClientGatewayLzReceiver.sol index ced95357..f1ccf7f8 100644 --- a/src/core/ClientGatewayLzReceiver.sol +++ b/src/core/ClientGatewayLzReceiver.sol @@ -12,6 +12,7 @@ abstract contract ClientGatewayLzReceiver is PausableUpgradeable, OAppReceiverUp error UnsupportedResponse(Action act); error UnexpectedResponse(uint64 nonce); error DepositShouldNotFailOnExocore(address token, address depositor); + error InvalidAddWhitelistTokensRequest(uint256 expectedLength, uint256 actualLength); modifier onlyCalledFromThis() { require( @@ -182,30 +183,33 @@ abstract contract ClientGatewayLzReceiver is PausableUpgradeable, OAppReceiverUp emit DepositThenDelegateResult(delegateSuccess, delegator, operator, token, amount); } - function afterReceiveRegisterTokensResponse(bytes calldata requestPayload, bytes calldata responsePayload) + function afterReceiveAddWhitelistTokensRequest(bytes calldata requestPayload) public onlyCalledFromThis whenNotPaused { - address[] memory tokens = abi.decode(requestPayload, (address[])); + uint8 count = uint8(requestPayload[0]); + uint256 expectedLength = count * TOKEN_ADDRESS_BYTES_LENTH + 1; + if (requestPayload.length != expectedLength) { + revert InvalidAddWhitelistTokensRequest(expectedLength, requestPayload.length); + } - bool success = (uint8(bytes1(responsePayload[0])) == 1); - if (success) { - for (uint256 i; i < tokens.length; i++) { - address token = tokens[i]; + for (uint256 i; i < count; i++) { + uint256 start = i * TOKEN_ADDRESS_BYTES_LENTH + 1; + uint256 end = start + TOKEN_ADDRESS_BYTES_LENTH; + address token = address(bytes20(payload[start:end])); + + if (!isWhitelistedToken[token]) { isWhitelistedToken[token] = true; whitelistTokens.push(token); - // deploy the corresponding vault if not deployed before - if (token != VIRTUAL_STAKED_ETH_ADDRESS && address(tokenToVault[token]) == address(0)) { - _deployVault(token); - } - emit WhitelistTokenAdded(token); } - } - emit RegisterTokensResult(success); + // deploy the corresponding vault if not deployed before + if (token != VIRTUAL_STAKED_ETH_ADDRESS && address(tokenToVault[token]) == address(0)) { + _deployVault(token); + } + } } - } diff --git a/src/core/ExocoreGateway.sol b/src/core/ExocoreGateway.sol index 2e0b587a..f28c0cc2 100644 --- a/src/core/ExocoreGateway.sol +++ b/src/core/ExocoreGateway.sol @@ -131,9 +131,9 @@ contract ExocoreGateway is uint32 clientChainId, bytes32 clientChainGateway, uint32 addressLength, - string memory name, - string memory metaInfo, - string memory signatureType + string calldata name, + string calldata metaInfo, + string calldata signatureType ) public override(IOAppCore, OAppCoreUpgradeable) @@ -143,14 +143,74 @@ contract ExocoreGateway is _validatePeer(clientChainId, clientChainGateway); _registerClientChain( clientChainId, - uint32 addressLength, - string memory name, - string memory metaInfo, - string memory signatureType + addressLength, + name, + metaInfo, + signatureType ); super.setPeer(clientChainId, clientChainGateway); } + function addWhitelistTokens( + uint32 clientChainId, + bytes32[] calldata tokens, + uint8[] calldata decimals, + uint256[] calldata tvlLimits, + string[] calldata names, + string[] calldata metaData + ) external payable onlyOwner whenNotPaused { + _validateWhitelistTokensInput( + clientChainId, + tokens, + decimals, + tvlLimits, + names, + metadata + ); + + bool success = ASSETS_CONTRACT.registerTokens( + srcChainId, + tokens, + decimals, + tvlLimits, + string[] memory names, + string[] memory metaData + ); + + if (!success) { + revert AddWhitelistTokensFailed(); + } + + _sendInterchainMsg(clientChainId, Action.REQUEST_ADD_WHITELIST_TOKENS, abi.encodePacked(uint8(tokens.length), tokens)); + } + + function _validateWhitelistTokensInput( + uint32 clientChainId, + bytes32[] calldata tokens, + uint8[] calldata decimals, + uint256[] calldata tvlLimits, + string[] calldata names, + string[] calldata metaData + ) internal pure { + if (peers[clientChainId] != bytes32(0)) { + revert ClientChainIDNotRegisteredBefore(clientChainId); + } + + uint256 expectedLength = tokens.length; + if (expectedLength > type(uint8).max) { + revert WhitelistTokensListTooLong(); + } + + if ( + decimals.length != expectedLength || + tvlLimits.length != expectedLength || + names.length != expectedLength || + metaData.length != expectedLength + ) { + revert InvalidWhitelistTokensInput(); + } + } + function _validatePeer(uint32 clientChainId, bytes32 clientChainGateway) internal pure { require(clientChainId != uint32(0), "ExocoreGateway: zero value is not invalid endpoint id"); require(clientChainGateway != bytes32(0), "ExocoreGateway: client chain gateway cannot be empty"); @@ -159,17 +219,17 @@ contract ExocoreGateway is function _registerClientChain( uint32 clientChainID, uint32 addressLength, - string memory name, - string memory metaInfo, - string memory signatureType + string calldata name, + string calldata metaInfo, + string calldata signatureType ) internal { if (peers[clientChainId] == bytes32(0)) { bool success = ASSETS_CONTRACT.registerClientChain( - uint32 clientChainID, - uint32 addressLength, - string memory name, - string memory metaInfo, - string memory signatureType + clientChainID, + addressLength, + name, + metaInfo, + signatureType ); if (!success) { revert RegisterClientChainToExocoreFailed(clientChainId); @@ -199,47 +259,11 @@ contract ExocoreGateway is } } - function requestRegisterTokens(uint32 srcChainId, uint64 lzNonce, bytes calldata payload) - public - onlyCalledFromThis - { - uint8 count = uint8(payload[0]); - uint256 expectedLength = count * TOKEN_ADDRESS_BYTES_LENTH + 1; - _validatePayloadLength(payload, expectedLength, Action.REQUEST_DEPOSIT); - - bytes[] memory tokens = new bytes[](count); - uint8[] memory decimals = new uint8[](count); - uint256[] memory tvlLimits = new uint256[](count); - uint256 start; - uint256 end; - for (uint256 i; i < count; i++) { - start = i * TOKEN_ADDRESS_BYTES_LENTH + 1; - end = start + TOKEN_ADDRESS_BYTES_LENTH; - tokens[i] = payload[start:end]; - - start = count * TOKEN_ADDRESS_BYTES_LENTH + i * UINT8_BYTES_LENGTH + 1; - end = start + UINT8_BYTES_LENGTH; - decimals[i] = payload[start:end]; - - start = count * (TOKEN_ADDRESS_BYTES_LENTH + UINT8_BYTES_LENGTH) + i * UINT256_BYTES_LENGTH + 1; - end = start + UINT256_BYTES_LENGTH; - tvlLimits[i] = payload[start:end]; - } - - try ASSETS_CONTRACT.registerTokens(srcChainId, tokens, decimals, tvlLimits) returns (bool success) { - _sendInterchainMsg(srcChainId, Action.RESPOND, abi.encodePacked(lzNonce, success)); - } catch { - emit ExocorePrecompileError(ASSETS_PRECOMPILE_ADDRESS, lzNonce); - - _sendInterchainMsg(srcChainId, Action.RESPOND, abi.encodePacked(lzNonce, false)); - } - } - function requestDeposit(uint32 srcChainId, uint64 lzNonce, bytes calldata payload) public onlyCalledFromThis { _validatePayloadLength(payload, DEPOSIT_REQUEST_LENGTH, Action.REQUEST_DEPOSIT); - bytes calldata token = payload[:32]; - bytes calldata depositor = payload[32:64]; + bytes32 token = bytes32(payload[:32]); + bytes32 depositor = bytes32(payload[32:64]); uint256 amount = uint256(bytes32(payload[64:96])); (bool success, uint256 updatedBalance) = ASSETS_CONTRACT.depositTo(srcChainId, token, depositor, amount); @@ -258,8 +282,8 @@ contract ExocoreGateway is payload, WITHDRAW_PRINCIPAL_REQUEST_LENGTH, Action.REQUEST_WITHDRAW_PRINCIPAL_FROM_EXOCORE ); - bytes calldata token = payload[:32]; - bytes calldata withdrawer = payload[32:64]; + bytes32 token = bytes32(payload[:32]); + bytes32 withdrawer = bytes32(payload[32:64]); uint256 amount = uint256(bytes32(payload[64:96])); try ASSETS_CONTRACT.withdrawPrincipal(srcChainId, token, withdrawer, amount) returns ( @@ -279,8 +303,8 @@ contract ExocoreGateway is { _validatePayloadLength(payload, CLAIM_REWARD_REQUEST_LENGTH, Action.REQUEST_WITHDRAW_REWARD_FROM_EXOCORE); - bytes calldata token = payload[:32]; - bytes calldata withdrawer = payload[32:64]; + bytes32 token = bytes32(payload[:32]); + bytes32 withdrawer = bytes32(payload[32:64]); uint256 amount = uint256(bytes32(payload[64:96])); try CLAIM_REWARD_CONTRACT.claimReward(srcChainId, token, withdrawer, amount) returns ( @@ -297,9 +321,9 @@ contract ExocoreGateway is function requestDelegateTo(uint32 srcChainId, uint64 lzNonce, bytes calldata payload) public onlyCalledFromThis { _validatePayloadLength(payload, DELEGATE_REQUEST_LENGTH, Action.REQUEST_DELEGATE_TO); - bytes calldata token = payload[:32]; - bytes calldata delegator = payload[32:64]; - bytes calldata operator = payload[64:106]; + bytes32 token = bytes32(payload[:32]); + bytes32 delegator = bytes32(payload[32:64]); + bytes32 operator = bytes32(payload[64:106]); uint256 amount = uint256(bytes32(payload[106:138])); try DELEGATION_CONTRACT.delegateToThroughClientChain(srcChainId, lzNonce, token, delegator, operator, amount) @@ -318,9 +342,9 @@ contract ExocoreGateway is { _validatePayloadLength(payload, UNDELEGATE_REQUEST_LENGTH, Action.REQUEST_UNDELEGATE_FROM); - bytes memory token = payload[:32]; - bytes memory delegator = payload[32:64]; - bytes memory operator = payload[64:106]; + bytes32 token = bytes32(payload[:32]); + bytes32 delegator = bytes32(payload[32:64]); + bytes32 operator = bytes32(payload[64:106]); uint256 amount = uint256(bytes32(payload[106:138])); try DELEGATION_CONTRACT.undelegateFromThroughClientChain( @@ -340,9 +364,9 @@ contract ExocoreGateway is { _validatePayloadLength(payload, DEPOSIT_THEN_DELEGATE_REQUEST_LENGTH, Action.REQUEST_DEPOSIT_THEN_DELEGATE_TO); - bytes calldata token = payload[:32]; - bytes calldata depositor = payload[32:64]; - bytes calldata operator = payload[64:106]; + bytes32 token = bytes32(payload[:32]); + bytes32 depositor = bytes32(payload[32:64]); + bytes32 operator = bytes32(payload[64:106]); uint256 amount = uint256(bytes32(payload[106:138])); // while some of the code from requestDeposit and requestDelegateTo is duplicated here, diff --git a/src/interfaces/precompiles/IAssets.sol b/src/interfaces/precompiles/IAssets.sol index dbaacfe3..477b6c24 100644 --- a/src/interfaces/precompiles/IAssets.sol +++ b/src/interfaces/precompiles/IAssets.sol @@ -24,8 +24,8 @@ interface IAssets { /// @param opAmount The amount to deposit function depositTo( uint32 clientChainID, - bytes memory assetsAddress, - bytes memory stakerAddress, + bytes32 assetsAddress, + bytes32 stakerAddress, uint256 opAmount) external returns (bool success, uint256 latestAssetState); @@ -40,8 +40,8 @@ interface IAssets { /// @param opAmount The withdraw amount function withdrawPrincipal( uint32 clientChainID, - bytes memory assetsAddress, - bytes memory withdrawAddress, + bytes32 assetsAddress, + bytes32 withdrawAddress, uint256 opAmount ) external returns (bool success, uint256 latestAssetState); @@ -58,9 +58,9 @@ interface IAssets { function registerClientChain( uint32 clientChainID, uint32 addressLength, - string memory name, - string memory metaInfo, - string memory signatureType + string calldata name, + string calldata metaInfo, + string calldata signatureType ) external returns (bool success); /// TRANSACTIONS @@ -71,8 +71,10 @@ interface IAssets { /// @param tokens The token addresses that would be registered to exocore function registerTokens( uint32 clientChainID, - bytes[] memory tokens, - uint8[] memory decimals, - uint256[] memory tvlLimit + bytes32[] calldata tokens, + uint8[] calldata decimals, + uint256[] calldata tvlLimits, + string[] calldata names, + string[] calldata metaData ) external returns (bool success); } \ No newline at end of file diff --git a/src/interfaces/precompiles/IClaimReward.sol b/src/interfaces/precompiles/IClaimReward.sol index b8b59944..5f8a9ae9 100644 --- a/src/interfaces/precompiles/IClaimReward.sol +++ b/src/interfaces/precompiles/IClaimReward.sol @@ -24,8 +24,8 @@ interface IClaimReward { /// @param opAmount The reward amount function claimReward( uint32 clientChainLzId, - bytes memory assetsAddress, - bytes memory withdrawRewardAddress, + bytes32 assetsAddress, + bytes32 withdrawRewardAddress, uint256 opAmount ) external returns (bool success, uint256 latestAssetState); diff --git a/src/interfaces/precompiles/IDelegation.sol b/src/interfaces/precompiles/IDelegation.sol index cb63808d..5c007062 100644 --- a/src/interfaces/precompiles/IDelegation.sol +++ b/src/interfaces/precompiles/IDelegation.sol @@ -26,9 +26,9 @@ interface IDelegation { function delegateToThroughClientChain( uint32 clientChainLzId, uint64 lzNonce, - bytes memory assetsAddress, - bytes memory stakerAddress, - bytes memory operatorAddr, + bytes32 assetsAddress, + bytes32 stakerAddress, + bytes32 operatorAddr, uint256 opAmount ) external returns (bool success); @@ -45,9 +45,9 @@ interface IDelegation { function undelegateFromThroughClientChain( uint32 clientChainLzId, uint64 lzNonce, - bytes memory assetsAddress, - bytes memory stakerAddress, - bytes memory operatorAddr, + bytes32 assetsAddress, + bytes32 stakerAddress, + bytes32 operatorAddr, uint256 opAmount ) external returns (bool success); diff --git a/src/storage/ClientChainGatewayStorage.sol b/src/storage/ClientChainGatewayStorage.sol index 6fb333b8..86eee8ab 100644 --- a/src/storage/ClientChainGatewayStorage.sol +++ b/src/storage/ClientChainGatewayStorage.sol @@ -27,8 +27,8 @@ contract ClientChainGatewayStorage is BootstrapStorage { uint128 internal constant DESTINATION_MSG_VALUE = 0; uint256 internal constant GWEI_TO_WEI = 1e9; address internal constant VIRTUAL_STAKED_ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; - uint8 internal constant ETH_DECIMALS = 18; IETHPOSDeposit internal constant ETH_POS = IETHPOSDeposit(0x00000000219ab540356cBB839Cbe05303d7705Fa); + uint256 internal constant TOKEN_ADDRESS_BYTES_LENTH = 32; uint256[40] private __gap; diff --git a/src/storage/ExocoreGatewayStorage.sol b/src/storage/ExocoreGatewayStorage.sol index daf8b357..0ecee8dc 100644 --- a/src/storage/ExocoreGatewayStorage.sol +++ b/src/storage/ExocoreGatewayStorage.sol @@ -32,6 +32,9 @@ contract ExocoreGatewayStorage is GatewayStorage { error InvalidRequestLength(Action act, uint256 expectedLength, uint256 actualLength); error DepositRequestShouldNotFail(uint32 srcChainId, uint64 lzNonce); error RegisterClientChainToExocoreFailed(uint32 clientChainId); + error AddWhitelistTokensFailed(); + error InvalidWhitelistTokensInput(); + error ClientChainIDNotRegisteredBefore(uint32 clientChainId); uint256[40] private __gap; diff --git a/src/storage/GatewayStorage.sol b/src/storage/GatewayStorage.sol index c5d9e21c..b1b5d621 100644 --- a/src/storage/GatewayStorage.sol +++ b/src/storage/GatewayStorage.sol @@ -10,7 +10,7 @@ contract GatewayStorage { REQUEST_UNDELEGATE_FROM, REQUEST_DEPOSIT_THEN_DELEGATE_TO, REQUEST_MARK_BOOTSTRAP, - REQUEST_REGISTER_TOKENS, + REQUEST_ADD_WHITELIST_TOKENS, RESPOND } diff --git a/test/foundry/unit/ClientChainGateway.t.sol b/test/foundry/unit/ClientChainGateway.t.sol index ae9c9202..406e9ddd 100644 --- a/test/foundry/unit/ClientChainGateway.t.sol +++ b/test/foundry/unit/ClientChainGateway.t.sol @@ -297,7 +297,6 @@ contract AddWhitelistTokens is SetUp { function test_RevertWhen_CallerNotOwner() public { address[] memory whitelistTokens = new address[](2); - uint256 nativeFee = clientGateway.quote(new bytes(TOKEN_ADDRESS_BYTES_LENTH * whitelistTokens.length + 2)); vm.startPrank(deployer.addr); vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, deployer.addr)); @@ -309,57 +308,15 @@ contract AddWhitelistTokens is SetUp { clientGateway.pause(); address[] memory whitelistTokens = new address[](2); - uint256 nativeFee = clientGateway.quote(new bytes(TOKEN_ADDRESS_BYTES_LENTH * whitelistTokens.length + 2)); vm.expectRevert(PausableUpgradeable.EnforcedPause.selector); clientGateway.addWhitelistTokens{value: nativeFee}(whitelistTokens); } - function test_RevertWhen_TokensListTooLong() public { - address[] memory whitelistTokens = new address[](256); - uint256 nativeFee = clientGateway.quote(new bytes(TOKEN_ADDRESS_BYTES_LENTH * whitelistTokens.length + 2)); - - vm.startPrank(exocoreValidatorSet.addr); - vm.expectRevert("ClientChainGateway: tokens length should not execeed 255"); - clientGateway.addWhitelistTokens{value: nativeFee}(whitelistTokens); - } - - function test_RevertWhen_HasZeroAddressToken() public { + function test_Revert_NotSupported() public { address[] memory whitelistTokens = new address[](2); - whitelistTokens[0] = address(restakeToken); - uint256 nativeFee = clientGateway.quote(new bytes(TOKEN_ADDRESS_BYTES_LENTH * whitelistTokens.length + 2)); vm.startPrank(exocoreValidatorSet.addr); - vm.expectRevert("ClientChainGateway: zero token address"); - clientGateway.addWhitelistTokens{value: nativeFee}(whitelistTokens); - } - - function test_RevertWhen_HasAlreadyWhitelistedToken() public { - // we use this hacking way to find the slot of `isWhitelistedToken(address(restakeToken))` and set its value to - // true - bytes32 whitelistedSlot = bytes32( - stdstore.target(address(clientGatewayLogic)).sig("isWhitelistedToken(address)").with_key( - address(restakeToken) - ).find() - ); - vm.store(address(clientGateway), whitelistedSlot, bytes32(uint256(1))); - - address[] memory whitelistTokens = new address[](1); - whitelistTokens[0] = address(restakeToken); - uint256 nativeFee = clientGateway.quote(new bytes(TOKEN_ADDRESS_BYTES_LENTH * whitelistTokens.length + 2)); - - vm.startPrank(exocoreValidatorSet.addr); - vm.expectRevert("ClientChainGateway: token should not be whitelisted before"); - clientGateway.addWhitelistTokens{value: nativeFee}(whitelistTokens); - } - - function test_SendMessage() public { - address[] memory whitelistTokens = new address[](1); - whitelistTokens[0] = address(restakeToken); - uint256 nativeFee = clientGateway.quote(new bytes(TOKEN_ADDRESS_BYTES_LENTH * whitelistTokens.length + 2)); - - vm.startPrank(exocoreValidatorSet.addr); - vm.expectEmit(true, true, true, true, address(clientGateway)); - emit MessageSent(GatewayStorage.Action.REQUEST_REGISTER_TOKENS, generateUID(1, true), 1, nativeFee); + vm.expectRevert("this function is not supported for client chain, please register on Exocore"); clientGateway.addWhitelistTokens{value: nativeFee}(whitelistTokens); } diff --git a/test/foundry/unit/ExocoreGateway.t.sol b/test/foundry/unit/ExocoreGateway.t.sol index ab58a455..5da99fcc 100644 --- a/test/foundry/unit/ExocoreGateway.t.sol +++ b/test/foundry/unit/ExocoreGateway.t.sol @@ -178,3 +178,89 @@ contract LzReceive is SetUp { } } + +contract AddWhitelistTokens is SetUp { + + using stdStorage for StdStorage; + + function test_RevertWhen_CallerNotOwner() public { + address[] memory whitelistTokens = new address[](2); + uint256[] memory tvlLimits = new uint256[](2); + uint messageLength = TOKEN_ADDRESS_BYTES_LENTH * whitelistTokens.length + 2; + uint256 nativeFee = clientGateway.quote(new bytes()); + + vm.startPrank(deployer.addr); + vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, deployer.addr)); + clientGateway.addWhitelistTokens{value: nativeFee}(whitelistTokens); + } + + function test_RevertWhen_Paused() public { + vm.startPrank(exocoreValidatorSet.addr); + clientGateway.pause(); + + address[] memory whitelistTokens = new address[](2); + uint256 nativeFee = clientGateway.quote(new bytes(TOKEN_ADDRESS_BYTES_LENTH * whitelistTokens.length + 2)); + vm.expectRevert(PausableUpgradeable.EnforcedPause.selector); + clientGateway.addWhitelistTokens{value: nativeFee}(whitelistTokens); + } + + function test_RevertWhen_TokensListTooLong() public { + address[] memory whitelistTokens = new address[](256); + uint256 nativeFee = clientGateway.quote(new bytes(TOKEN_ADDRESS_BYTES_LENTH * whitelistTokens.length + 2)); + + vm.startPrank(exocoreValidatorSet.addr); + vm.expectRevert("ClientChainGateway: tokens length should not execeed 255"); + clientGateway.addWhitelistTokens{value: nativeFee}(whitelistTokens); + } + + function test_RevertWhen_LengthNotMatch() public { + address[] memory whitelistTokens = new address[](2); + uint256[] memory tvlLimits = new uint256[](3); + uint256 nativeFee = clientGateway.quote(new bytes(TOKEN_ADDRESS_BYTES_LENTH * whitelistTokens.length + 2)); + + vm.startPrank(exocoreValidatorSet.addr); + vm.expectRevert("ClientChainGateway: tokens length should not execeed 255"); + clientGateway.addWhitelistTokens{value: nativeFee}(whitelistTokens); + } + + function test_RevertWhen_HasZeroAddressToken() public { + address[] memory whitelistTokens = new address[](2); + whitelistTokens[0] = address(restakeToken); + uint256 nativeFee = clientGateway.quote(new bytes(TOKEN_ADDRESS_BYTES_LENTH * whitelistTokens.length + 2)); + + vm.startPrank(exocoreValidatorSet.addr); + vm.expectRevert("ClientChainGateway: zero token address"); + clientGateway.addWhitelistTokens{value: nativeFee}(whitelistTokens); + } + + function test_RevertWhen_HasAlreadyWhitelistedToken() public { + // we use this hacking way to find the slot of `isWhitelistedToken(address(restakeToken))` and set its value to + // true + bytes32 whitelistedSlot = bytes32( + stdstore.target(address(clientGatewayLogic)).sig("isWhitelistedToken(address)").with_key( + address(restakeToken) + ).find() + ); + vm.store(address(clientGateway), whitelistedSlot, bytes32(uint256(1))); + + address[] memory whitelistTokens = new address[](1); + whitelistTokens[0] = address(restakeToken); + uint256 nativeFee = clientGateway.quote(new bytes(TOKEN_ADDRESS_BYTES_LENTH * whitelistTokens.length + 2)); + + vm.startPrank(exocoreValidatorSet.addr); + vm.expectRevert("ClientChainGateway: token should not be whitelisted before"); + clientGateway.addWhitelistTokens{value: nativeFee}(whitelistTokens); + } + + function test_SendMessage() public { + address[] memory whitelistTokens = new address[](1); + whitelistTokens[0] = address(restakeToken); + uint256 nativeFee = clientGateway.quote(new bytes(TOKEN_ADDRESS_BYTES_LENTH * whitelistTokens.length + 2)); + + vm.startPrank(exocoreValidatorSet.addr); + vm.expectEmit(true, true, true, true, address(clientGateway)); + emit MessageSent(GatewayStorage.Action.REQUEST_REGISTER_TOKENS, generateUID(1, true), 1, nativeFee); + clientGateway.addWhitelistTokens{value: nativeFee}(whitelistTokens); + } + +} \ No newline at end of file From 607bb7115fab4c446cc7fd69f11d5da5f5090886 Mon Sep 17 00:00:00 2001 From: adu Date: Fri, 28 Jun 2024 10:12:25 +0800 Subject: [PATCH 05/19] refactor: add addWhitelistTokens on Exocore side --- script/13_DepositValidator.s.sol | 1 - script/3_Setup.s.sol | 51 ++- script/BaseScript.sol | 2 + src/core/Bootstrap.sol | 2 +- src/core/ClientChainGateway.sol | 3 +- src/core/ClientGatewayLzReceiver.sol | 12 +- src/core/ExocoreGateway.sol | 163 +++++--- src/core/NativeRestakingController.sol | 14 +- src/interfaces/IExocoreGateway.sol | 17 + src/interfaces/ITokenWhitelister.sol | 2 +- src/interfaces/precompiles/IAssets.sol | 41 +- src/interfaces/precompiles/IClaimReward.sol | 6 +- src/interfaces/precompiles/IDelegation.sol | 14 +- src/storage/ExocoreGatewayStorage.sol | 12 +- test/foundry/Delegation.t.sol | 4 +- test/foundry/DepositThenDelegateTo.t.sol | 24 +- test/foundry/DepositWithdrawPrinciple.t.sol | 6 +- test/foundry/ExocoreDeployer.t.sol | 101 ++--- test/foundry/WithdrawReward.t.sol | 2 +- test/foundry/unit/ClientChainGateway.t.sol | 20 +- test/foundry/unit/ExocoreGateway.t.sol | 429 ++++++++++++++++++-- test/mocks/AssetsMock.sol | 39 +- test/mocks/ExocoreGatewayMock.sol | 260 +++++++++--- 23 files changed, 920 insertions(+), 305 deletions(-) diff --git a/script/13_DepositValidator.s.sol b/script/13_DepositValidator.s.sol index dd13f51e..2b431aae 100644 --- a/script/13_DepositValidator.s.sol +++ b/script/13_DepositValidator.s.sol @@ -28,7 +28,6 @@ contract DepositScript is BaseScript { uint256 internal constant GENESIS_BLOCK_TIMESTAMP = 1_695_902_400; uint256 internal constant SECONDS_PER_SLOT = 12; - address constant VIRTUAL_STAKED_ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; uint256 constant GWEI_TO_WEI = 1e9; function setUp() public virtual override { diff --git a/script/3_Setup.s.sol b/script/3_Setup.s.sol index 6fae4825..bdb636f6 100644 --- a/script/3_Setup.s.sol +++ b/script/3_Setup.s.sol @@ -65,7 +65,7 @@ contract SetupScript is BaseScript { vm.stopBroadcast(); // 2. setup Exocore contracts to make them ready for sending and receiving messages from client chain - // gateway + // gateway, and register client chain meta data as well as well as adding tokens to whtielist to enable restaking vm.selectFork(exocore); // Exocore validator set should be the owner of these contracts and only owner could setup contracts state vm.startBroadcast(exocoreValidatorSet.privateKey); @@ -75,16 +75,45 @@ contract SetupScript is BaseScript { address(clientGateway), address(clientChainLzEndpoint) ); } - // this would also register clientChainId to Exocore native module - exocoreGateway.setPeer(clientChainId, address(clientGateway).toBytes32()); - vm.stopBroadcast(); - - // 3. we should register whitelist tokens to exocore - vm.selectFork(clientChain); - // Exocore validator set should be the owner of these contracts and only owner could add whitelist tokens - vm.startBroadcast(exocoreValidatorSet.privateKey); - whitelistTokens.push(address(restakeToken)); - clientGateway.addWhitelistTokens(whitelistTokens); + // first register clientChainId to Exocore native module and set peer for client chain gateway to be ready for messaging + exocoreGateway.registerOrUpdateClientChain( + clientChainId, + address(clientGateway).toBytes32(), + 20, + "clientChain", + "", + "secp256k1" + ); + // second add whitelist tokens and their meta data on Exocore side to enable LST Restaking and Native Restaking, + // and this would also add token addresses to client chain gateway's whitelist + bytes32[] memory whitelistTokensBytes32 = new bytes32[](2); + uint8[] memory decimals = new uint8[](2); + uint256[] memory tvlLimits = new uint256[](2); + string[] memory names = new string[](2); + string[] memory metaData = new string[](2); + + whitelistTokensBytes32[0] = bytes32(bytes20(address(restakeToken))); + decimals[0] = restakeToken.decimals(); + tvlLimits[0] = 1e10 ether; + names[0] = "RestakeToken"; + metaData[0] = ""; + + whitelistTokensBytes32[1] = bytes32(bytes20(VIRTUAL_STAKED_ETH_ADDRESS)); + decimals[1] = 18; + tvlLimits[1] = 1e8 ether; + names[1] = "RestakeToken"; + metaData[1] = ""; + + uint messageLength = TOKEN_ADDRESS_BYTES_LENTH * whitelistTokensBytes32.length + 2; + uint256 nativeFee = exocoreGateway.quote(clientChainId, new bytes(messageLength)); + exocoreGateway.addWhitelistTokens{value: nativeFee}( + clientChainId, + whitelistTokensBytes32, + decimals, + tvlLimits, + names, + metaData + ); vm.stopBroadcast(); } diff --git a/script/BaseScript.sol b/script/BaseScript.sol index bd647b9b..abaeefc1 100644 --- a/script/BaseScript.sol +++ b/script/BaseScript.sol @@ -69,6 +69,8 @@ contract BaseScript is Script { uint256 constant DEPOSIT_AMOUNT = 1 ether; uint256 constant WITHDRAW_AMOUNT = 1 ether; + address internal constant VIRTUAL_STAKED_ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; + uint256 internal constant TOKEN_ADDRESS_BYTES_LENTH = 32; bool useExocorePrecompileMock; bool useEndpointMock; diff --git a/src/core/Bootstrap.sol b/src/core/Bootstrap.sol index 5abba722..b28b28be 100644 --- a/src/core/Bootstrap.sol +++ b/src/core/Bootstrap.sol @@ -155,7 +155,7 @@ contract Bootstrap is } // implementation of ITokenWhitelister - function addWhitelistTokens(address[] calldata tokens) external payable beforeLocked onlyOwner whenNotPaused { + function addWhitelistTokens(address[] calldata tokens) external beforeLocked onlyOwner whenNotPaused { _addWhitelistTokens(tokens); } diff --git a/src/core/ClientChainGateway.sol b/src/core/ClientChainGateway.sol index dbad8292..e1c9feae 100644 --- a/src/core/ClientChainGateway.sol +++ b/src/core/ClientChainGateway.sol @@ -78,7 +78,6 @@ contract ClientChainGateway is this.afterReceiveWithdrawRewardResponse.selector; _registeredResponseHooks[Action.REQUEST_DEPOSIT_THEN_DELEGATE_TO] = this.afterReceiveDepositThenDelegateToResponse.selector; - _registeredResponseHooks[Action.REQUEST_REGISTER_TOKENS] = this.afterReceiveRegisterTokensResponse.selector; _whiteListFunctionSelectors[Action.REQUEST_ADD_WHITELIST_TOKENS] = this.afterReceiveAddWhitelistTokensRequest.selector; @@ -122,7 +121,7 @@ contract ClientChainGateway is _unpause(); } - function addWhitelistTokens(address[] calldata tokens) external payable { + function addWhitelistTokens(address[] calldata tokens) external onlyOwner whenNotPaused { revert("this function is not supported for client chain, please register on Exocore"); } diff --git a/src/core/ClientGatewayLzReceiver.sol b/src/core/ClientGatewayLzReceiver.sol index f1ccf7f8..94c0a4ec 100644 --- a/src/core/ClientGatewayLzReceiver.sol +++ b/src/core/ClientGatewayLzReceiver.sol @@ -197,18 +197,18 @@ abstract contract ClientGatewayLzReceiver is PausableUpgradeable, OAppReceiverUp for (uint256 i; i < count; i++) { uint256 start = i * TOKEN_ADDRESS_BYTES_LENTH + 1; uint256 end = start + TOKEN_ADDRESS_BYTES_LENTH; - address token = address(bytes20(payload[start:end])); + address token = address(bytes20(requestPayload[start:end])); if (!isWhitelistedToken[token]) { isWhitelistedToken[token] = true; whitelistTokens.push(token); - emit WhitelistTokenAdded(token); - } + // deploy the corresponding vault if not deployed before + if (token != VIRTUAL_STAKED_ETH_ADDRESS && address(tokenToVault[token]) == address(0)) { + _deployVault(token); + } - // deploy the corresponding vault if not deployed before - if (token != VIRTUAL_STAKED_ETH_ADDRESS && address(tokenToVault[token]) == address(0)) { - _deployVault(token); + emit WhitelistTokenAdded(token); } } } diff --git a/src/core/ExocoreGateway.sol b/src/core/ExocoreGateway.sol index f28c0cc2..eb59ea73 100644 --- a/src/core/ExocoreGateway.sol +++ b/src/core/ExocoreGateway.sol @@ -74,7 +74,6 @@ contract ExocoreGateway is _whiteListFunctionSelectors[Action.REQUEST_WITHDRAW_REWARD_FROM_EXOCORE] = this.requestWithdrawReward.selector; _whiteListFunctionSelectors[Action.REQUEST_DEPOSIT_THEN_DELEGATE_TO] = this.requestDepositThenDelegateTo.selector; - _whiteListFunctionSelectors[Action.REQUEST_REGISTER_TOKENS] = this.requestRegisterTokens.selector; } function pause() external { @@ -106,7 +105,7 @@ contract ExocoreGateway is for (uint256 i = 0; i < clientChainIds.length; i++) { uint32 clientChainId = clientChainIds[i]; if (!chainToBootstrapped[clientChainId]) { - _sendInterchainMsg(clientChainId, Action.REQUEST_MARK_BOOTSTRAP, ""); + _sendInterchainMsg(clientChainId, Action.REQUEST_MARK_BOOTSTRAP, "", true); // TODO: should this be marked only upon receiving a response? chainToBootstrapped[clientChainId] = true; } @@ -127,16 +126,15 @@ contract ExocoreGateway is * @dev Indicates that the peer is trusted to send LayerZero messages to this OApp. * @dev Peer is a bytes32 to accommodate non-evm chains. */ - function setPeer( + function registerOrUpdateClientChain( uint32 clientChainId, bytes32 clientChainGateway, - uint32 addressLength, + uint8 addressLength, string calldata name, string calldata metaInfo, string calldata signatureType ) public - override(IOAppCore, OAppCoreUpgradeable) onlyOwner whenNotPaused { @@ -149,6 +147,24 @@ contract ExocoreGateway is signatureType ); super.setPeer(clientChainId, clientChainGateway); + + if (!isRegisteredClientChain[clientChainId]) { + isRegisteredClientChain[clientChainId] = true; + emit ClientChainRegistered(clientChainId); + } else { + emit ClientChainUpdated(clientChainId); + } + } + + function setPeer(uint32 clientChainId, bytes32 clientChainGateway) + public + override(IOAppCore, OAppCoreUpgradeable) + onlyOwner + whenNotPaused + { + require(isRegisteredClientChain[clientChainId], "ExocoreGateway: client chain should be registered before setting peer to change peer address"); + + super.setPeer(clientChainId, clientChainGateway); } function addWhitelistTokens( @@ -165,23 +181,72 @@ contract ExocoreGateway is decimals, tvlLimits, names, - metadata + metaData ); - bool success = ASSETS_CONTRACT.registerTokens( - srcChainId, - tokens, - decimals, + for (uint i; i < tokens.length; i++) { + require(tokens[i] != bytes32(0), "ExocoreGateway: token cannot be zero address"); + require(!isWhitelistedToken[tokens[i]], "ExocoreGateway: token has already been added to whitelist before"); + require(tvlLimits[i] >0, "ExocoreGateway: tvl limit should not be zero"); + + bool success = ASSETS_CONTRACT.registerToken( + clientChainId, + abi.encodePacked(tokens[i]), + decimals[i], + tvlLimits[i], + names[i], + metaData[i] + ); + + if (success) { + isWhitelistedToken[tokens[i]] = true; + } else { + revert AddWhitelistTokenFailed(tokens[i]); + } + + emit WhitelistTokenAdded(clientChainId, tokens[i]); + } + + _sendInterchainMsg(clientChainId, Action.REQUEST_ADD_WHITELIST_TOKENS, abi.encodePacked(uint8(tokens.length), tokens), false); + } + + function updateWhitelistedTokens( + uint32 clientChainId, + bytes32[] calldata tokens, + uint8[] calldata decimals, + uint256[] calldata tvlLimits, + string[] calldata names, + string[] calldata metaData + ) external onlyOwner whenNotPaused { + _validateWhitelistTokensInput( + clientChainId, + tokens, + decimals, tvlLimits, - string[] memory names, - string[] memory metaData + names, + metaData ); - if (!success) { - revert AddWhitelistTokensFailed(); - } + for (uint i; i < tokens.length; i++) { + require(tokens[i] != bytes32(0), "ExocoreGateway: token cannot be zero address"); + require(isWhitelistedToken[tokens[i]], "ExocoreGateway: token has not been added to whitelist before"); + require(tvlLimits[i] >0, "ExocoreGateway: tvl limit should not be zero"); + + bool success = ASSETS_CONTRACT.registerToken( + clientChainId, + abi.encodePacked(tokens[i]), + decimals[i], + tvlLimits[i], + names[i], + metaData[i] + ); - _sendInterchainMsg(clientChainId, Action.REQUEST_ADD_WHITELIST_TOKENS, abi.encodePacked(uint8(tokens.length), tokens)); + if (!success) { + revert UpdateWhitelistTokenFailed(tokens[i]); + } + + emit WhitelistTokenUpdated(clientChainId, tokens[i]); + } } function _validateWhitelistTokensInput( @@ -191,8 +256,8 @@ contract ExocoreGateway is uint256[] calldata tvlLimits, string[] calldata names, string[] calldata metaData - ) internal pure { - if (peers[clientChainId] != bytes32(0)) { + ) internal view { + if (!isRegisteredClientChain[clientChainId]) { revert ClientChainIDNotRegisteredBefore(clientChainId); } @@ -217,15 +282,15 @@ contract ExocoreGateway is } function _registerClientChain( - uint32 clientChainID, - uint32 addressLength, + uint32 clientChainId, + uint8 addressLength, string calldata name, string calldata metaInfo, string calldata signatureType ) internal { if (peers[clientChainId] == bytes32(0)) { bool success = ASSETS_CONTRACT.registerClientChain( - clientChainID, + clientChainId, addressLength, name, metaInfo, @@ -262,8 +327,8 @@ contract ExocoreGateway is function requestDeposit(uint32 srcChainId, uint64 lzNonce, bytes calldata payload) public onlyCalledFromThis { _validatePayloadLength(payload, DEPOSIT_REQUEST_LENGTH, Action.REQUEST_DEPOSIT); - bytes32 token = bytes32(payload[:32]); - bytes32 depositor = bytes32(payload[32:64]); + bytes memory token = payload[:32]; + bytes memory depositor = payload[32:64]; uint256 amount = uint256(bytes32(payload[64:96])); (bool success, uint256 updatedBalance) = ASSETS_CONTRACT.depositTo(srcChainId, token, depositor, amount); @@ -271,7 +336,7 @@ contract ExocoreGateway is revert DepositRequestShouldNotFail(srcChainId, lzNonce); } - _sendInterchainMsg(srcChainId, Action.RESPOND, abi.encodePacked(lzNonce, success, updatedBalance)); + _sendInterchainMsg(srcChainId, Action.RESPOND, abi.encodePacked(lzNonce, success, updatedBalance), true); } function requestWithdrawPrincipal(uint32 srcChainId, uint64 lzNonce, bytes calldata payload) @@ -282,18 +347,18 @@ contract ExocoreGateway is payload, WITHDRAW_PRINCIPAL_REQUEST_LENGTH, Action.REQUEST_WITHDRAW_PRINCIPAL_FROM_EXOCORE ); - bytes32 token = bytes32(payload[:32]); - bytes32 withdrawer = bytes32(payload[32:64]); + bytes memory token = payload[:32]; + bytes memory withdrawer = payload[32:64]; uint256 amount = uint256(bytes32(payload[64:96])); try ASSETS_CONTRACT.withdrawPrincipal(srcChainId, token, withdrawer, amount) returns ( bool success, uint256 updatedBalance ) { - _sendInterchainMsg(srcChainId, Action.RESPOND, abi.encodePacked(lzNonce, success, updatedBalance)); + _sendInterchainMsg(srcChainId, Action.RESPOND, abi.encodePacked(lzNonce, success, updatedBalance), true); } catch { emit ExocorePrecompileError(ASSETS_PRECOMPILE_ADDRESS, lzNonce); - _sendInterchainMsg(srcChainId, Action.RESPOND, abi.encodePacked(lzNonce, false, uint256(0))); + _sendInterchainMsg(srcChainId, Action.RESPOND, abi.encodePacked(lzNonce, false, uint256(0)), true); } } @@ -303,36 +368,36 @@ contract ExocoreGateway is { _validatePayloadLength(payload, CLAIM_REWARD_REQUEST_LENGTH, Action.REQUEST_WITHDRAW_REWARD_FROM_EXOCORE); - bytes32 token = bytes32(payload[:32]); - bytes32 withdrawer = bytes32(payload[32:64]); + bytes memory token = payload[:32]; + bytes memory withdrawer = payload[32:64]; uint256 amount = uint256(bytes32(payload[64:96])); try CLAIM_REWARD_CONTRACT.claimReward(srcChainId, token, withdrawer, amount) returns ( bool success, uint256 updatedBalance ) { - _sendInterchainMsg(srcChainId, Action.RESPOND, abi.encodePacked(lzNonce, success, updatedBalance)); + _sendInterchainMsg(srcChainId, Action.RESPOND, abi.encodePacked(lzNonce, success, updatedBalance), true); } catch { emit ExocorePrecompileError(CLAIM_REWARD_PRECOMPILE_ADDRESS, lzNonce); - _sendInterchainMsg(srcChainId, Action.RESPOND, abi.encodePacked(lzNonce, false, uint256(0))); + _sendInterchainMsg(srcChainId, Action.RESPOND, abi.encodePacked(lzNonce, false, uint256(0)), true); } } function requestDelegateTo(uint32 srcChainId, uint64 lzNonce, bytes calldata payload) public onlyCalledFromThis { _validatePayloadLength(payload, DELEGATE_REQUEST_LENGTH, Action.REQUEST_DELEGATE_TO); - bytes32 token = bytes32(payload[:32]); - bytes32 delegator = bytes32(payload[32:64]); - bytes32 operator = bytes32(payload[64:106]); + bytes memory token = payload[:32]; + bytes memory delegator = payload[32:64]; + bytes memory operator = payload[64:106]; uint256 amount = uint256(bytes32(payload[106:138])); try DELEGATION_CONTRACT.delegateToThroughClientChain(srcChainId, lzNonce, token, delegator, operator, amount) returns (bool success) { - _sendInterchainMsg(srcChainId, Action.RESPOND, abi.encodePacked(lzNonce, success)); + _sendInterchainMsg(srcChainId, Action.RESPOND, abi.encodePacked(lzNonce, success), true); } catch { emit ExocorePrecompileError(DELEGATION_PRECOMPILE_ADDRESS, lzNonce); - _sendInterchainMsg(srcChainId, Action.RESPOND, abi.encodePacked(lzNonce, false)); + _sendInterchainMsg(srcChainId, Action.RESPOND, abi.encodePacked(lzNonce, false), true); } } @@ -342,19 +407,19 @@ contract ExocoreGateway is { _validatePayloadLength(payload, UNDELEGATE_REQUEST_LENGTH, Action.REQUEST_UNDELEGATE_FROM); - bytes32 token = bytes32(payload[:32]); - bytes32 delegator = bytes32(payload[32:64]); - bytes32 operator = bytes32(payload[64:106]); + bytes memory token = payload[:32]; + bytes memory delegator = payload[32:64]; + bytes memory operator = payload[64:106]; uint256 amount = uint256(bytes32(payload[106:138])); try DELEGATION_CONTRACT.undelegateFromThroughClientChain( srcChainId, lzNonce, token, delegator, operator, amount ) returns (bool success) { - _sendInterchainMsg(srcChainId, Action.RESPOND, abi.encodePacked(lzNonce, success)); + _sendInterchainMsg(srcChainId, Action.RESPOND, abi.encodePacked(lzNonce, success), true); } catch { emit ExocorePrecompileError(DELEGATION_PRECOMPILE_ADDRESS, lzNonce); - _sendInterchainMsg(srcChainId, Action.RESPOND, abi.encodePacked(lzNonce, false)); + _sendInterchainMsg(srcChainId, Action.RESPOND, abi.encodePacked(lzNonce, false), true); } } @@ -364,9 +429,9 @@ contract ExocoreGateway is { _validatePayloadLength(payload, DEPOSIT_THEN_DELEGATE_REQUEST_LENGTH, Action.REQUEST_DEPOSIT_THEN_DELEGATE_TO); - bytes32 token = bytes32(payload[:32]); - bytes32 depositor = bytes32(payload[32:64]); - bytes32 operator = bytes32(payload[64:106]); + bytes memory token = payload[:32]; + bytes memory depositor = payload[32:64]; + bytes memory operator = payload[64:106]; uint256 amount = uint256(bytes32(payload[106:138])); // while some of the code from requestDeposit and requestDelegateTo is duplicated here, @@ -381,10 +446,10 @@ contract ExocoreGateway is } try DELEGATION_CONTRACT.delegateToThroughClientChain(srcChainId, lzNonce, token, depositor, operator, amount) returns (bool delegateSuccess) { - _sendInterchainMsg(srcChainId, Action.RESPOND, abi.encodePacked(lzNonce, delegateSuccess, updatedBalance)); + _sendInterchainMsg(srcChainId, Action.RESPOND, abi.encodePacked(lzNonce, delegateSuccess, updatedBalance), true); } catch { emit ExocorePrecompileError(DELEGATION_PRECOMPILE_ADDRESS, lzNonce); - _sendInterchainMsg(srcChainId, Action.RESPOND, abi.encodePacked(lzNonce, false, updatedBalance)); + _sendInterchainMsg(srcChainId, Action.RESPOND, abi.encodePacked(lzNonce, false, updatedBalance), true); } } @@ -394,7 +459,7 @@ contract ExocoreGateway is } } - function _sendInterchainMsg(uint32 srcChainId, Action act, bytes memory actionArgs) internal whenNotPaused { + function _sendInterchainMsg(uint32 srcChainId, Action act, bytes memory actionArgs, bool payByApp) internal whenNotPaused { bytes memory payload = abi.encodePacked(act, actionArgs); bytes memory options = OptionsBuilder.newOptions().addExecutorLzReceiveOption( DESTINATION_GAS_LIMIT, DESTINATION_MSG_VALUE @@ -402,7 +467,7 @@ contract ExocoreGateway is MessagingFee memory fee = _quote(srcChainId, payload, options, false); MessagingReceipt memory receipt = - _lzSend(srcChainId, payload, options, MessagingFee(fee.nativeFee, 0), exocoreValidatorSetAddress, true); + _lzSend(srcChainId, payload, options, MessagingFee(fee.nativeFee, 0), exocoreValidatorSetAddress, payByApp); emit MessageSent(act, receipt.guid, receipt.nonce, receipt.fee.nativeFee); } diff --git a/src/core/NativeRestakingController.sol b/src/core/NativeRestakingController.sol index b0097fb7..5c53a4e2 100644 --- a/src/core/NativeRestakingController.sol +++ b/src/core/NativeRestakingController.sol @@ -20,11 +20,17 @@ abstract contract NativeRestakingController is using ValidatorContainer for bytes32[]; + modifier nativeRestakingEnabled() { + require(isWhitelistedToken[VIRTUAL_STAKED_ETH_ADDRESS], "NativeRestakingController: native restaking is not enabled"); + _; + } + function stake(bytes calldata pubkey, bytes calldata signature, bytes32 depositDataRoot) external payable whenNotPaused nonReentrant + nativeRestakingEnabled { require(msg.value == 32 ether, "NativeRestakingController: stake value must be exactly 32 ether"); @@ -37,7 +43,7 @@ abstract contract NativeRestakingController is emit StakedWithCapsule(msg.sender, address(capsule)); } - function createExoCapsule() public whenNotPaused returns (address) { + function createExoCapsule() public whenNotPaused nativeRestakingEnabled returns (address) { require( address(ownerToCapsule[msg.sender]) == address(0), "NativeRestakingController: message sender has already created the capsule" @@ -61,7 +67,7 @@ abstract contract NativeRestakingController is function depositBeaconChainValidator( bytes32[] calldata validatorContainer, IExoCapsule.ValidatorContainerProof calldata proof - ) external payable whenNotPaused nonReentrant { + ) external payable whenNotPaused nonReentrant nativeRestakingEnabled { IExoCapsule capsule = _getCapsule(msg.sender); capsule.verifyDepositProof(validatorContainer, proof); @@ -78,13 +84,13 @@ abstract contract NativeRestakingController is IExoCapsule.ValidatorContainerProof calldata validatorProof, bytes32[] calldata withdrawalContainer, IExoCapsule.WithdrawalContainerProof calldata withdrawalProof - ) external payable whenNotPaused nonReentrant {} + ) external payable whenNotPaused nonReentrant nativeRestakingEnabled {} function processBeaconChainFullWithdrawal( bytes32[] calldata validatorContainer, IExoCapsule.ValidatorContainerProof calldata validatorProof, bytes32[] calldata withdrawalContainer, IExoCapsule.WithdrawalContainerProof calldata withdrawalProof - ) external payable whenNotPaused nonReentrant {} + ) external payable whenNotPaused nonReentrant nativeRestakingEnabled {} } diff --git a/src/interfaces/IExocoreGateway.sol b/src/interfaces/IExocoreGateway.sol index 1b8f0228..b5ebc875 100644 --- a/src/interfaces/IExocoreGateway.sol +++ b/src/interfaces/IExocoreGateway.sol @@ -7,4 +7,21 @@ interface IExocoreGateway is IOAppReceiver, IOAppCore { function quote(uint32 srcChainid, bytes memory _message) external view returns (uint256 nativeFee); + function registerOrUpdateClientChain( + uint32 clientChainId, + bytes32 clientChainGateway, + uint8 addressLength, + string calldata name, + string calldata metaInfo, + string calldata signatureType + ) external; + + function addWhitelistTokens( + uint32 clientChainId, + bytes32[] calldata tokens, + uint8[] calldata decimals, + uint256[] calldata tvlLimits, + string[] calldata names, + string[] calldata metaData + ) external payable; } diff --git a/src/interfaces/ITokenWhitelister.sol b/src/interfaces/ITokenWhitelister.sol index 8931ebf6..55010297 100644 --- a/src/interfaces/ITokenWhitelister.sol +++ b/src/interfaces/ITokenWhitelister.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.19; interface ITokenWhitelister { - function addWhitelistTokens(address[] calldata tokens) external payable; + function addWhitelistTokens(address[] calldata tokens) external; function getWhitelistedTokensCount() external returns (uint256); } diff --git a/src/interfaces/precompiles/IAssets.sol b/src/interfaces/precompiles/IAssets.sol index 477b6c24..499dc69b 100644 --- a/src/interfaces/precompiles/IAssets.sol +++ b/src/interfaces/precompiles/IAssets.sol @@ -16,32 +16,25 @@ interface IAssets { /// @dev deposit the client chain assets for the staker, /// that will change the state in deposit module /// Note that this address cannot be a module account. - /// @param clientChainID is the layerZero chainID if it is supported. - // It might be allocated by Exocore when the client chain isn't supported - // by layerZero + /// @param clientChainLzID The LzID of client chain /// @param assetsAddress The client chain asset address /// @param stakerAddress The staker address /// @param opAmount The amount to deposit - function depositTo( - uint32 clientChainID, - bytes32 assetsAddress, - bytes32 stakerAddress, - uint256 opAmount) external - returns (bool success, uint256 latestAssetState); + function depositTo(uint32 clientChainLzID, bytes memory assetsAddress, bytes memory stakerAddress, uint256 opAmount) + external + returns (bool success, uint256 latestAssetState); /// TRANSACTIONS /// @dev withdraw To the staker, that will change the state in withdraw module /// Note that this address cannot be a module account. - /// @param clientChainID is the layerZero chainID if it is supported. - // It might be allocated by Exocore when the client chain isn't supported - // by layerZero + /// @param clientChainLzID The LzID of client chain /// @param assetsAddress The client chain asset Address /// @param withdrawAddress The withdraw address /// @param opAmount The withdraw amount function withdrawPrincipal( - uint32 clientChainID, - bytes32 assetsAddress, - bytes32 withdrawAddress, + uint32 clientChainLzID, + bytes memory assetsAddress, + bytes memory withdrawAddress, uint256 opAmount ) external returns (bool success, uint256 latestAssetState); @@ -57,24 +50,24 @@ interface IAssets { // by layerZero function registerClientChain( uint32 clientChainID, - uint32 addressLength, + uint8 addressLength, string calldata name, string calldata metaInfo, string calldata signatureType ) external returns (bool success); /// TRANSACTIONS - /// @dev register unwhitelisted token addresses to exocore + /// @dev register unwhitelisted token address to exocore assets module /// @param clientChainID is the layerZero chainID if it is supported. // It might be allocated by Exocore when the client chain isn't supported // by layerZero - /// @param tokens The token addresses that would be registered to exocore - function registerTokens( + /// @param token The token address that would be registered to exocore + function registerToken( uint32 clientChainID, - bytes32[] calldata tokens, - uint8[] calldata decimals, - uint256[] calldata tvlLimits, - string[] calldata names, - string[] calldata metaData + bytes calldata token, + uint8 decimals, + uint256 tvlLimit, + string calldata name, + string calldata metaData ) external returns (bool success); } \ No newline at end of file diff --git a/src/interfaces/precompiles/IClaimReward.sol b/src/interfaces/precompiles/IClaimReward.sol index 5f8a9ae9..68acd483 100644 --- a/src/interfaces/precompiles/IClaimReward.sol +++ b/src/interfaces/precompiles/IClaimReward.sol @@ -24,9 +24,9 @@ interface IClaimReward { /// @param opAmount The reward amount function claimReward( uint32 clientChainLzId, - bytes32 assetsAddress, - bytes32 withdrawRewardAddress, + bytes memory assetsAddress, + bytes memory withdrawRewardAddress, uint256 opAmount ) external returns (bool success, uint256 latestAssetState); -} +} \ No newline at end of file diff --git a/src/interfaces/precompiles/IDelegation.sol b/src/interfaces/precompiles/IDelegation.sol index 5c007062..9cefd37e 100644 --- a/src/interfaces/precompiles/IDelegation.sol +++ b/src/interfaces/precompiles/IDelegation.sol @@ -26,9 +26,9 @@ interface IDelegation { function delegateToThroughClientChain( uint32 clientChainLzId, uint64 lzNonce, - bytes32 assetsAddress, - bytes32 stakerAddress, - bytes32 operatorAddr, + bytes memory assetsAddress, + bytes memory stakerAddress, + bytes memory operatorAddr, uint256 opAmount ) external returns (bool success); @@ -45,10 +45,10 @@ interface IDelegation { function undelegateFromThroughClientChain( uint32 clientChainLzId, uint64 lzNonce, - bytes32 assetsAddress, - bytes32 stakerAddress, - bytes32 operatorAddr, + bytes memory assetsAddress, + bytes memory stakerAddress, + bytes memory operatorAddr, uint256 opAmount ) external returns (bool success); -} +} \ No newline at end of file diff --git a/src/storage/ExocoreGatewayStorage.sol b/src/storage/ExocoreGatewayStorage.sol index 0ecee8dc..af0b3506 100644 --- a/src/storage/ExocoreGatewayStorage.sol +++ b/src/storage/ExocoreGatewayStorage.sol @@ -23,18 +23,26 @@ contract ExocoreGatewayStorage is GatewayStorage { uint128 internal constant DESTINATION_GAS_LIMIT = 500_000; uint128 internal constant DESTINATION_MSG_VALUE = 0; - mapping(uint32 id => bool) public chainToBootstrapped; + mapping(uint32 clienChainId => bool) public chainToBootstrapped; + mapping(uint32 clienChainId => bool registered) public isRegisteredClientChain; + mapping(bytes32 token => bool whitelisted) public isWhitelistedToken; event ExocorePrecompileError(address indexed precompile, uint64 nonce); + event ClientChainRegistered(uint32 clientChainId); + event ClientChainUpdated(uint32 clientChainId); + event WhitelistTokenAdded(uint32 clientChainId, bytes32 token); + event WhitelistTokenUpdated(uint32 clientChainId, bytes32 token); error RequestExecuteFailed(Action act, uint64 nonce, bytes reason); error PrecompileCallFailed(bytes4 selector_, bytes reason); error InvalidRequestLength(Action act, uint256 expectedLength, uint256 actualLength); error DepositRequestShouldNotFail(uint32 srcChainId, uint64 lzNonce); error RegisterClientChainToExocoreFailed(uint32 clientChainId); - error AddWhitelistTokensFailed(); + error AddWhitelistTokenFailed(bytes32 token); + error UpdateWhitelistTokenFailed(bytes32 token); error InvalidWhitelistTokensInput(); error ClientChainIDNotRegisteredBefore(uint32 clientChainId); + error WhitelistTokensListTooLong(); uint256[40] private __gap; diff --git a/test/foundry/Delegation.t.sol b/test/foundry/Delegation.t.sol index 11867ad1..e39e64d4 100644 --- a/test/foundry/Delegation.t.sol +++ b/test/foundry/Delegation.t.sol @@ -87,7 +87,7 @@ contract DelegateTest is ExocoreDeployer { // 1. first user call client chain gateway to delegate /// estimate the messaging fee that would be charged from user - uint64 delegateRequestNonce = 2; + uint64 delegateRequestNonce = 1; bytes memory delegateRequestPayload = abi.encodePacked( GatewayStorage.Action.REQUEST_DELEGATE_TO, abi.encodePacked(bytes32(bytes20(address(restakeToken)))), @@ -198,7 +198,7 @@ contract DelegateTest is ExocoreDeployer { // 1. first user call client chain gateway to undelegate /// estimate the messaging fee that would be charged from user - uint64 undelegateRequestNonce = 3; + uint64 undelegateRequestNonce = 2; bytes memory undelegateRequestPayload = abi.encodePacked( GatewayStorage.Action.REQUEST_UNDELEGATE_FROM, abi.encodePacked(bytes32(bytes20(address(restakeToken)))), diff --git a/test/foundry/DepositThenDelegateTo.t.sol b/test/foundry/DepositThenDelegateTo.t.sol index aea4b5ae..bd2bc35d 100644 --- a/test/foundry/DepositThenDelegateTo.t.sol +++ b/test/foundry/DepositThenDelegateTo.t.sol @@ -48,7 +48,8 @@ contract DepositThenDelegateToTest is ExocoreDeployer { deal(delegator, 1e22); deal(address(exocoreGateway), 1e22); - uint64 lzNonce = 2; + uint64 requestLzNonce = 1; + uint64 responseLzNonce = 2; uint256 delegateAmount = 10_000; // before all operations we should add whitelist tokens @@ -65,8 +66,8 @@ contract DepositThenDelegateToTest is ExocoreDeployer { vm.stopPrank(); (bytes32 requestId, bytes memory requestPayload) = - _testRequest(delegator, operatorAddress, lzNonce, delegateAmount); - _testResponse(requestId, requestPayload, delegator, relayer, operatorAddress, lzNonce, delegateAmount); + _testRequest(delegator, operatorAddress, requestLzNonce, delegateAmount); + _testResponse(requestId, requestPayload, delegator, relayer, operatorAddress, requestLzNonce, responseLzNonce, delegateAmount); } function _testRequest(address delegator, string memory operatorAddress, uint64 lzNonce, uint256 delegateAmount) @@ -118,17 +119,18 @@ contract DepositThenDelegateToTest is ExocoreDeployer { address delegator, address relayer, string memory operatorAddress, - uint64 lzNonce, + uint64 requestLzNonce, + uint64 responseLzNonce, uint256 delegateAmount ) private { - bytes memory responsePayload = abi.encodePacked(GatewayStorage.Action.RESPOND, lzNonce, true, delegateAmount); + bytes memory responsePayload = abi.encodePacked(GatewayStorage.Action.RESPOND, requestLzNonce, true, delegateAmount); uint256 responseNativeFee = exocoreGateway.quote(clientChainId, responsePayload); - bytes32 responseId = generateUID(lzNonce, false); + bytes32 responseId = generateUID(responseLzNonce, false); vm.expectEmit(DELEGATION_PRECOMPILE_ADDRESS); emit DelegateRequestProcessed( clientChainId, - lzNonce, + requestLzNonce, abi.encodePacked(bytes32(bytes20(address(restakeToken)))), abi.encodePacked(bytes32(bytes20(delegator))), operatorAddress, @@ -141,16 +143,16 @@ contract DepositThenDelegateToTest is ExocoreDeployer { clientChainId, address(exocoreGateway), address(clientGateway).toBytes32(), - lzNonce, // outbound nonce not inbound, only equals because it's the first tx + responseLzNonce, // outbound nonce not inbound, only equals because it's the first tx responsePayload ); vm.expectEmit(address(exocoreGateway)); - emit MessageSent(GatewayStorage.Action.RESPOND, responseId, lzNonce, responseNativeFee); + emit MessageSent(GatewayStorage.Action.RESPOND, responseId, responseLzNonce, responseNativeFee); vm.startPrank(relayer); exocoreLzEndpoint.lzReceive( - Origin(clientChainId, address(clientGateway).toBytes32(), lzNonce), + Origin(clientChainId, address(clientGateway).toBytes32(), requestLzNonce), address(exocoreGateway), requestId, requestPayload, @@ -181,7 +183,7 @@ contract DepositThenDelegateToTest is ExocoreDeployer { vm.startPrank(relayer); clientChainLzEndpoint.lzReceive( - Origin(exocoreChainId, address(exocoreGateway).toBytes32(), lzNonce), + Origin(exocoreChainId, address(exocoreGateway).toBytes32(), responseLzNonce), address(clientGateway), responseId, responsePayload, diff --git a/test/foundry/DepositWithdrawPrinciple.t.sol b/test/foundry/DepositWithdrawPrinciple.t.sol index dfbec57a..283e4e9c 100644 --- a/test/foundry/DepositWithdrawPrinciple.t.sol +++ b/test/foundry/DepositWithdrawPrinciple.t.sol @@ -65,7 +65,7 @@ contract DepositWithdrawPrincipalTest is ExocoreDeployer { // first user call client chain gateway to deposit // estimate l0 relay fee that the user should pay - uint64 depositRequestNonce = 2; + uint64 depositRequestNonce = 1; bytes memory depositRequestPayload = abi.encodePacked( GatewayStorage.Action.REQUEST_DEPOSIT, bytes32(bytes20(address(restakeToken))), @@ -147,7 +147,7 @@ contract DepositWithdrawPrincipalTest is ExocoreDeployer { // first user call client chain gateway to withdraw // estimate l0 relay fee that the user should pay - uint64 withdrawRequestNonce = 3; + uint64 withdrawRequestNonce = 2; bytes memory withdrawRequestPayload = abi.encodePacked( GatewayStorage.Action.REQUEST_WITHDRAW_PRINCIPAL_FROM_EXOCORE, bytes32(bytes20(address(restakeToken))), @@ -288,7 +288,7 @@ contract DepositWithdrawPrincipalTest is ExocoreDeployer { // through layerzero /// client chain layerzero endpoint should emit the message packet including deposit payload. - uint64 depositRequestNonce = 2; + uint64 depositRequestNonce = 1; uint256 depositAmount = uint256(_getEffectiveBalance(validatorContainer)) * GWEI_TO_WEI; bytes memory depositRequestPayload = abi.encodePacked( GatewayStorage.Action.REQUEST_DEPOSIT, diff --git a/test/foundry/ExocoreDeployer.t.sol b/test/foundry/ExocoreDeployer.t.sol index fb5ccb6f..ab5b8838 100644 --- a/test/foundry/ExocoreDeployer.t.sol +++ b/test/foundry/ExocoreDeployer.t.sol @@ -39,7 +39,7 @@ contract ExocoreDeployer is Test { using Endian for bytes32; Player[] players; - address[] whitelistTokens; + bytes32[] whitelistTokens; Player exocoreValidatorSet; address[] vaults; ERC20PresetFixedSupply restakeToken; @@ -121,73 +121,67 @@ contract ExocoreDeployer is Test { function test_AddWhitelistTokens() public { // transfer some gas fee to exocore validator set deal(exocoreValidatorSet.addr, 1e22); - // transfer some gas fee to exocore gateway as it has to pay for the relay fee to layerzero endpoint when - // sending back response - deal(address(exocoreGateway), 1e22); - whitelistTokens.push(address(restakeToken)); + uint8[] memory decimals = new uint8[](2); + uint256[] memory tvlLimits = new uint256[](2); + string[] memory names = new string[](2); + string[] memory metaData = new string[](2); + + whitelistTokens.push(bytes32(bytes20(address(restakeToken)))); + decimals[0] = 18; + tvlLimits[0] = 1e8 ether; + names[0] = "RestakeToken"; + metaData[0] = ""; + + whitelistTokens.push(bytes32(bytes20(VIRTUAL_STAKED_ETH_ADDRESS))); + decimals[1] = 18; + tvlLimits[1] = 1e8 ether; + names[1] = "NativeStakedETH"; + metaData[1] = ""; // -- add whitelist tokens workflow test -- vm.startPrank(exocoreValidatorSet.addr); - // first user call client chain gateway to add whitelist tokens + // first user call exocore gateway to add whitelist tokens // estimate l0 relay fee that the user should pay bytes memory registerTokensRequestPayload = abi.encodePacked( - GatewayStorage.Action.REQUEST_REGISTER_TOKENS, uint8(1), bytes32(bytes20(address(restakeToken))) + GatewayStorage.Action.REQUEST_ADD_WHITELIST_TOKENS, + uint8(whitelistTokens.length), + bytes32(bytes20(address(restakeToken))), + bytes32(bytes20(VIRTUAL_STAKED_ETH_ADDRESS)) ); uint256 registerTokensRequestNativeFee = clientGateway.quote(registerTokensRequestPayload); - bytes32 registerTokensRequestId = generateUID(1, true); - - // client chain layerzero endpoint should emit the message packet including deposit payload. - vm.expectEmit(true, true, true, true, address(clientChainLzEndpoint)); - emit NewPacket( - exocoreChainId, - address(clientGateway), - address(exocoreGateway).toBytes32(), - uint64(1), - registerTokensRequestPayload - ); - // client chain gateway should emit MessageSent event - vm.expectEmit(true, true, true, true, address(clientGateway)); - emit MessageSent( - GatewayStorage.Action.REQUEST_REGISTER_TOKENS, - registerTokensRequestId, - uint64(1), - registerTokensRequestNativeFee - ); - clientGateway.addWhitelistTokens{value: registerTokensRequestNativeFee}(whitelistTokens); - - // second layerzero relayers should watch the request message packet and relay the message to destination - // endpoint + bytes32 registerTokensRequestId = generateUID(1, false); - // exocore gateway should return response message to exocore network layerzero endpoint + // exocore layerzero endpoint should emit the message packet including whitelist tokens payload. vm.expectEmit(true, true, true, true, address(exocoreLzEndpoint)); - bytes memory registerTokensResponsePayload = abi.encodePacked(GatewayStorage.Action.RESPOND, uint64(1), true); - uint256 registerTokensResponseNativeFee = exocoreGateway.quote(clientChainId, registerTokensResponsePayload); - bytes32 registerTokensResponseId = generateUID(1, false); emit NewPacket( clientChainId, address(exocoreGateway), address(clientGateway).toBytes32(), uint64(1), - registerTokensResponsePayload + registerTokensRequestPayload ); - // exocore gateway should emit MessageSent event + // exocore gateway gateway should emit MessageSent event vm.expectEmit(true, true, true, true, address(exocoreGateway)); emit MessageSent( - GatewayStorage.Action.RESPOND, registerTokensResponseId, uint64(1), registerTokensResponseNativeFee - ); - exocoreLzEndpoint.lzReceive( - Origin(clientChainId, address(clientGateway).toBytes32(), uint64(1)), - address(exocoreGateway), + GatewayStorage.Action.REQUEST_ADD_WHITELIST_TOKENS, registerTokensRequestId, - registerTokensRequestPayload, - bytes("") + uint64(1), + registerTokensRequestNativeFee + ); + exocoreGateway.addWhitelistTokens{value: registerTokensRequestNativeFee}( + clientChainId, + whitelistTokens, + decimals, + tvlLimits, + names, + metaData ); - // third layerzero relayers should watch the response message packet and relay the message to source chain + // second layerzero relayers should watch the request message packet and relay the message to destination // endpoint address expectedVault = Create2.computeAddress( @@ -195,16 +189,15 @@ contract ExocoreDeployer is Test { keccak256(abi.encodePacked(BEACON_PROXY_BYTECODE, abi.encode(address(vaultBeacon), ""))), address(clientGateway) ); - // client chain gateway should execute the response hook and emit depositResult event vm.expectEmit(true, true, true, true, address(clientGateway)); emit VaultCreated(address(restakeToken), expectedVault); emit WhitelistTokenAdded(address(restakeToken)); - emit RegisterTokensResult(true); + emit WhitelistTokenAdded(VIRTUAL_STAKED_ETH_ADDRESS); clientChainLzEndpoint.lzReceive( Origin(exocoreChainId, address(exocoreGateway).toBytes32(), uint64(1)), address(clientGateway), - registerTokensResponseId, - registerTokensResponsePayload, + registerTokensRequestId, + registerTokensRequestPayload, bytes("") ); @@ -212,6 +205,7 @@ contract ExocoreDeployer is Test { vault = Vault(address(clientGateway.tokenToVault(address(restakeToken)))); assertEq(address(vault), expectedVault); assertTrue(clientGateway.isWhitelistedToken(address(restakeToken))); + assertTrue(clientGateway.isWhitelistedToken(VIRTUAL_STAKED_ETH_ADDRESS)); vm.stopPrank(); } @@ -321,9 +315,16 @@ contract ExocoreDeployer is Test { vm.startPrank(exocoreValidatorSet.addr); // as LzReceivers, gateway should set bytes(sourceChainGatewayAddress+thisAddress) as trusted remote to receive - // messages + // messages. On Exocore side, this is done by calling registerClientChain clientGateway.setPeer(exocoreChainId, address(exocoreGateway).toBytes32()); - exocoreGateway.setPeer(clientChainId, address(clientGateway).toBytes32()); + exocoreGateway.registerOrUpdateClientChain( + clientChainId, + address(clientGateway).toBytes32(), + 20, + "clientChain", + "", + "secp256k1" + ); vm.stopPrank(); } diff --git a/test/foundry/WithdrawReward.t.sol b/test/foundry/WithdrawReward.t.sol index 21cd58c3..a39cb973 100644 --- a/test/foundry/WithdrawReward.t.sol +++ b/test/foundry/WithdrawReward.t.sol @@ -37,7 +37,7 @@ contract WithdrawRewardTest is ExocoreDeployer { // first user call client chain gateway to withdraw // estimate l0 relay fee that the user should pay - uint64 withdrawRequestNonce = 2; + uint64 withdrawRequestNonce = 1; bytes memory withdrawRequestPayload = abi.encodePacked( GatewayStorage.Action.REQUEST_WITHDRAW_REWARD_FROM_EXOCORE, bytes32(bytes20(address(restakeToken))), diff --git a/test/foundry/unit/ClientChainGateway.t.sol b/test/foundry/unit/ClientChainGateway.t.sol index 406e9ddd..e875256e 100644 --- a/test/foundry/unit/ClientChainGateway.t.sol +++ b/test/foundry/unit/ClientChainGateway.t.sol @@ -38,20 +38,6 @@ contract SetUp is Test { address addr; } - // bytes32 token + bytes32 depositor + uint256 amount - uint256 internal constant DEPOSIT_REQUEST_LENGTH = 96; - // bytes32 token + bytes32 delegator + bytes(42) operator + uint256 amount - uint256 internal constant DELEGATE_REQUEST_LENGTH = 138; - // bytes32 token + bytes32 delegator + bytes(42) operator + uint256 amount - uint256 internal constant UNDELEGATE_REQUEST_LENGTH = 138; - // bytes32 token + bytes32 withdrawer + uint256 amount - uint256 internal constant WITHDRAW_PRINCIPAL_REQUEST_LENGTH = 96; - // bytes32 token + bytes32 withdrawer + uint256 amount - uint256 internal constant CLAIM_REWARD_REQUEST_LENGTH = 96; - // bytes32 token + bytes32 delegator + bytes(42) operator + uint256 amount - uint256 internal constant DEPOSIT_THEN_DELEGATE_REQUEST_LENGTH = DELEGATE_REQUEST_LENGTH; - uint256 internal constant TOKEN_ADDRESS_BYTES_LENTH = 32; - Player[] players; address[] whitelistTokens; Player exocoreValidatorSet; @@ -300,7 +286,7 @@ contract AddWhitelistTokens is SetUp { vm.startPrank(deployer.addr); vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, deployer.addr)); - clientGateway.addWhitelistTokens{value: nativeFee}(whitelistTokens); + clientGateway.addWhitelistTokens(whitelistTokens); } function test_RevertWhen_Paused() public { @@ -309,7 +295,7 @@ contract AddWhitelistTokens is SetUp { address[] memory whitelistTokens = new address[](2); vm.expectRevert(PausableUpgradeable.EnforcedPause.selector); - clientGateway.addWhitelistTokens{value: nativeFee}(whitelistTokens); + clientGateway.addWhitelistTokens(whitelistTokens); } function test_Revert_NotSupported() public { @@ -317,7 +303,7 @@ contract AddWhitelistTokens is SetUp { vm.startPrank(exocoreValidatorSet.addr); vm.expectRevert("this function is not supported for client chain, please register on Exocore"); - clientGateway.addWhitelistTokens{value: nativeFee}(whitelistTokens); + clientGateway.addWhitelistTokens(whitelistTokens); } } diff --git a/test/foundry/unit/ExocoreGateway.t.sol b/test/foundry/unit/ExocoreGateway.t.sol index 5da99fcc..f67cf347 100644 --- a/test/foundry/unit/ExocoreGateway.t.sol +++ b/test/foundry/unit/ExocoreGateway.t.sol @@ -19,6 +19,7 @@ import "src/core/ExocoreGateway.sol"; import {Vault} from "src/core/Vault.sol"; import {GatewayStorage} from "src/storage/GatewayStorage.sol"; +import {ExocoreGatewayStorage} from "src/storage/ExocoreGatewayStorage.sol"; contract SetUp is Test { @@ -46,6 +47,7 @@ contract SetUp is Test { event Paused(address account); event Unpaused(address account); event ExocorePrecompileError(address indexed precompile, uint64 nonce); + event MessageSent(GatewayStorage.Action indexed act, bytes32 packetId, uint64 nonce, uint256 nativeFee); error EnforcedPause(); error ExpectedPause(); @@ -70,6 +72,9 @@ contract SetUp is Test { vm.etch(CLAIM_REWARD_PRECOMPILE_ADDRESS, WithdrawRewardMockCode); _deploy(); + + vm.deal(exocoreValidatorSet.addr, 100 ether); + vm.deal(deployer.addr, 100 ether); } function _deploy() internal { @@ -89,10 +94,17 @@ contract SetUp is Test { exocoreGateway.initialize(payable(exocoreValidatorSet.addr)); vm.stopPrank(); - vm.prank(exocoreValidatorSet.addr); - exocoreGateway.setPeer(clientChainId, address(clientGateway).toBytes32()); - + vm.startPrank(exocoreValidatorSet.addr); exocoreLzEndpoint.setDestLzEndpoint(address(clientGateway), address(clientLzEndpoint)); + exocoreGateway.registerOrUpdateClientChain( + clientChainId, + address(clientGateway).toBytes32(), + 20, + "clientChain", + "", + "secp256k1" + ); + vm.stopPrank(); // transfer some gas fee to exocore gateway as it has to pay for the relay fee to layerzero endpoint when // sending back response @@ -182,85 +194,412 @@ contract LzReceive is SetUp { contract AddWhitelistTokens is SetUp { using stdStorage for StdStorage; + using AddressCast for address; + + uint256 internal constant TOKEN_ADDRESS_BYTES_LENTH = 32; + + event WhitelistTokenAdded(uint32 clientChainId, bytes32 token); + + bytes32[] whitelistTokens; + uint8[] decimals; + uint256[] tvlLimits; + string[] names; + string[] metaData; function test_RevertWhen_CallerNotOwner() public { - address[] memory whitelistTokens = new address[](2); - uint256[] memory tvlLimits = new uint256[](2); + _prepareInputs(2); + + uint messageLength = TOKEN_ADDRESS_BYTES_LENTH * whitelistTokens.length + 2; + uint256 nativeFee = exocoreGateway.quote(clientChainId, new bytes(messageLength)); + + vm.startPrank(deployer.addr); + vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, deployer.addr)); + exocoreGateway.addWhitelistTokens{value: nativeFee}( + clientChainId, + whitelistTokens, + decimals, + tvlLimits, + names, + metaData + ); + } + + function test_RevertWhen_Paused() public { + vm.startPrank(exocoreValidatorSet.addr); + exocoreGateway.pause(); + + _prepareInputs(2); + + uint messageLength = TOKEN_ADDRESS_BYTES_LENTH * whitelistTokens.length + 2; + uint256 nativeFee = exocoreGateway.quote(clientChainId, new bytes(messageLength)); + vm.expectRevert(PausableUpgradeable.EnforcedPause.selector); + exocoreGateway.addWhitelistTokens{value: nativeFee}( + clientChainId, + whitelistTokens, + decimals, + tvlLimits, + names, + metaData + ); + } + + function test_RevertWhen_ClientChainNotRegisteredBefore() public { + uint32 anotherClientChain = 3; + _prepareInputs(2); + + uint messageLength = TOKEN_ADDRESS_BYTES_LENTH * whitelistTokens.length + 2; + uint256 nativeFee = exocoreGateway.quote(clientChainId, new bytes(messageLength)); + + vm.startPrank(exocoreValidatorSet.addr); + vm.expectRevert(abi.encodeWithSelector(ExocoreGatewayStorage.ClientChainIDNotRegisteredBefore.selector, anotherClientChain)); + exocoreGateway.addWhitelistTokens{value: nativeFee}( + anotherClientChain, + whitelistTokens, + decimals, + tvlLimits, + names, + metaData + ); + } + + function test_RevertWhen_TokensListTooLong() public { + _prepareInputs(256); + + uint messageLength = TOKEN_ADDRESS_BYTES_LENTH * whitelistTokens.length + 2; + uint256 nativeFee = exocoreGateway.quote(clientChainId, new bytes(messageLength)); + + vm.startPrank(exocoreValidatorSet.addr); + vm.expectRevert(abi.encodeWithSelector(ExocoreGatewayStorage.WhitelistTokensListTooLong.selector)); + exocoreGateway.addWhitelistTokens{value: nativeFee}( + clientChainId, + whitelistTokens, + decimals, + tvlLimits, + names, + metaData + ); + } + + function test_RevertWhen_LengthNotMatch() public { + _prepareInputs(2); + decimals.push(18); + + uint messageLength = TOKEN_ADDRESS_BYTES_LENTH * whitelistTokens.length + 2; + uint256 nativeFee = exocoreGateway.quote(clientChainId, new bytes(messageLength)); + + vm.startPrank(exocoreValidatorSet.addr); + vm.expectRevert(abi.encodeWithSelector(ExocoreGatewayStorage.InvalidWhitelistTokensInput.selector)); + exocoreGateway.addWhitelistTokens{value: nativeFee}( + clientChainId, + whitelistTokens, + decimals, + tvlLimits, + names, + metaData + ); + } + + function test_RevertWhen_HasZeroAddressToken() public { + _prepareInputs(2); + whitelistTokens[0] = bytes32(bytes20(address(restakeToken))); + tvlLimits[0] = 1e8 ether; + tvlLimits[1] = 1e8 ether; + + uint messageLength = TOKEN_ADDRESS_BYTES_LENTH * whitelistTokens.length + 2; + uint256 nativeFee = exocoreGateway.quote(clientChainId, new bytes(messageLength)); + + vm.startPrank(exocoreValidatorSet.addr); + vm.expectRevert("ExocoreGateway: token cannot be zero address"); + exocoreGateway.addWhitelistTokens{value: nativeFee}( + clientChainId, + whitelistTokens, + decimals, + tvlLimits, + names, + metaData + ); + } + + function test_RevertWhen_HasZeroTVMLimit() public { + _prepareInputs(1); + whitelistTokens[0] = bytes32(bytes20(address(restakeToken))); + + uint messageLength = TOKEN_ADDRESS_BYTES_LENTH * whitelistTokens.length + 2; + uint256 nativeFee = exocoreGateway.quote(clientChainId, new bytes(messageLength)); + + vm.startPrank(exocoreValidatorSet.addr); + vm.expectRevert("ExocoreGateway: tvl limit should not be zero"); + exocoreGateway.addWhitelistTokens{value: nativeFee}( + clientChainId, + whitelistTokens, + decimals, + tvlLimits, + names, + metaData + ); + } + + function test_Success_AddWhiteListTokens() public { + _prepareInputs(1); + whitelistTokens[0] = bytes32(bytes20(address(restakeToken))); + decimals[0] = 18; + tvlLimits[0] = 1e8 ether; + names[0] = "RestakeToken"; + metaData[0] = ""; + uint messageLength = TOKEN_ADDRESS_BYTES_LENTH * whitelistTokens.length + 2; - uint256 nativeFee = clientGateway.quote(new bytes()); + uint256 nativeFee = exocoreGateway.quote(clientChainId, new bytes(messageLength)); + + vm.startPrank(exocoreValidatorSet.addr); + vm.expectEmit(true, true, true, true, address(exocoreGateway)); + emit WhitelistTokenAdded(clientChainId, whitelistTokens[0]); + emit MessageSent(GatewayStorage.Action.REQUEST_ADD_WHITELIST_TOKENS, generateUID(1, false), 1, nativeFee); + exocoreGateway.addWhitelistTokens{value: nativeFee}( + clientChainId, + whitelistTokens, + decimals, + tvlLimits, + names, + metaData + ); + } + + function _prepareInputs(uint256 listLength) internal { + whitelistTokens = new bytes32[](listLength); + decimals = new uint8[](listLength); + tvlLimits = new uint256[](listLength); + names = new string[](listLength); + metaData = new string[](listLength); + } + + function generateUID(uint64 nonce, bool fromClientChainToExocore) internal view returns (bytes32 uid) { + if (fromClientChainToExocore) { + uid = GUID.generate( + nonce, clientChainId, address(clientGateway), exocoreChainId, address(exocoreGateway).toBytes32() + ); + } else { + uid = GUID.generate( + nonce, exocoreChainId, address(exocoreGateway), clientChainId, address(clientGateway).toBytes32() + ); + } + } +} + +contract UpdateWhitelistTokens is SetUp { + + using AddressCast for address; + + uint256 internal constant TOKEN_ADDRESS_BYTES_LENTH = 32; + + event WhitelistTokenUpdated(uint32 clientChainId, bytes32 token); + + bytes32[] whitelistTokens; + uint8[] decimals; + uint256[] tvlLimits; + string[] names; + string[] metaData; + + function test_RevertWhen_CallerNotOwner() public { + _prepareInputs(2); vm.startPrank(deployer.addr); vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, deployer.addr)); - clientGateway.addWhitelistTokens{value: nativeFee}(whitelistTokens); + exocoreGateway.updateWhitelistedTokens( + clientChainId, + whitelistTokens, + decimals, + tvlLimits, + names, + metaData + ); } function test_RevertWhen_Paused() public { vm.startPrank(exocoreValidatorSet.addr); - clientGateway.pause(); + exocoreGateway.pause(); + + _prepareInputs(2); - address[] memory whitelistTokens = new address[](2); - uint256 nativeFee = clientGateway.quote(new bytes(TOKEN_ADDRESS_BYTES_LENTH * whitelistTokens.length + 2)); vm.expectRevert(PausableUpgradeable.EnforcedPause.selector); - clientGateway.addWhitelistTokens{value: nativeFee}(whitelistTokens); + exocoreGateway.updateWhitelistedTokens( + clientChainId, + whitelistTokens, + decimals, + tvlLimits, + names, + metaData + ); + } + + function test_RevertWhen_ClientChainNotRegisteredBefore() public { + uint32 anotherClientChain = 3; + _prepareInputs(2); + + vm.startPrank(exocoreValidatorSet.addr); + vm.expectRevert(abi.encodeWithSelector(ExocoreGatewayStorage.ClientChainIDNotRegisteredBefore.selector, anotherClientChain)); + exocoreGateway.updateWhitelistedTokens( + anotherClientChain, + whitelistTokens, + decimals, + tvlLimits, + names, + metaData + ); } function test_RevertWhen_TokensListTooLong() public { - address[] memory whitelistTokens = new address[](256); - uint256 nativeFee = clientGateway.quote(new bytes(TOKEN_ADDRESS_BYTES_LENTH * whitelistTokens.length + 2)); + _prepareInputs(256); vm.startPrank(exocoreValidatorSet.addr); - vm.expectRevert("ClientChainGateway: tokens length should not execeed 255"); - clientGateway.addWhitelistTokens{value: nativeFee}(whitelistTokens); + vm.expectRevert(abi.encodeWithSelector(ExocoreGatewayStorage.WhitelistTokensListTooLong.selector)); + exocoreGateway.updateWhitelistedTokens( + clientChainId, + whitelistTokens, + decimals, + tvlLimits, + names, + metaData + ); } function test_RevertWhen_LengthNotMatch() public { - address[] memory whitelistTokens = new address[](2); - uint256[] memory tvlLimits = new uint256[](3); - uint256 nativeFee = clientGateway.quote(new bytes(TOKEN_ADDRESS_BYTES_LENTH * whitelistTokens.length + 2)); + _prepareInputs(2); + decimals.push(18); vm.startPrank(exocoreValidatorSet.addr); - vm.expectRevert("ClientChainGateway: tokens length should not execeed 255"); - clientGateway.addWhitelistTokens{value: nativeFee}(whitelistTokens); + vm.expectRevert(abi.encodeWithSelector(ExocoreGatewayStorage.InvalidWhitelistTokensInput.selector)); + exocoreGateway.updateWhitelistedTokens( + clientChainId, + whitelistTokens, + decimals, + tvlLimits, + names, + metaData + ); } function test_RevertWhen_HasZeroAddressToken() public { - address[] memory whitelistTokens = new address[](2); - whitelistTokens[0] = address(restakeToken); - uint256 nativeFee = clientGateway.quote(new bytes(TOKEN_ADDRESS_BYTES_LENTH * whitelistTokens.length + 2)); + _prepareInputs(1); + whitelistTokens[0] = bytes32(bytes20(address(restakeToken))); + decimals[0] = 18; + tvlLimits[0] = 1e8 ether; + names[0] = "RestakeToken"; + metaData[0] = ""; + _addWhitelistTokens(clientChainId, whitelistTokens, decimals, tvlLimits, names, metaData); + + _prepareInputs(2); + whitelistTokens[0] = bytes32(bytes20(address(restakeToken))); + tvlLimits[0] = 1e8 ether; + tvlLimits[1] = 1e8 ether; vm.startPrank(exocoreValidatorSet.addr); - vm.expectRevert("ClientChainGateway: zero token address"); - clientGateway.addWhitelistTokens{value: nativeFee}(whitelistTokens); + vm.expectRevert("ExocoreGateway: token cannot be zero address"); + exocoreGateway.updateWhitelistedTokens( + clientChainId, + whitelistTokens, + decimals, + tvlLimits, + names, + metaData + ); } - function test_RevertWhen_HasAlreadyWhitelistedToken() public { - // we use this hacking way to find the slot of `isWhitelistedToken(address(restakeToken))` and set its value to - // true - bytes32 whitelistedSlot = bytes32( - stdstore.target(address(clientGatewayLogic)).sig("isWhitelistedToken(address)").with_key( - address(restakeToken) - ).find() + function test_RevertWhen_HasZeroTVMLimit() public { + _prepareInputs(1); + whitelistTokens[0] = bytes32(bytes20(address(restakeToken))); + decimals[0] = 18; + tvlLimits[0] = 1e8 ether; + names[0] = "RestakeToken"; + metaData[0] = ""; + _addWhitelistTokens(clientChainId, whitelistTokens, decimals, tvlLimits, names, metaData); + + tvlLimits[0] = 0; + + vm.startPrank(exocoreValidatorSet.addr); + vm.expectRevert("ExocoreGateway: tvl limit should not be zero"); + exocoreGateway.updateWhitelistedTokens( + clientChainId, + whitelistTokens, + decimals, + tvlLimits, + names, + metaData ); - vm.store(address(clientGateway), whitelistedSlot, bytes32(uint256(1))); + } - address[] memory whitelistTokens = new address[](1); - whitelistTokens[0] = address(restakeToken); - uint256 nativeFee = clientGateway.quote(new bytes(TOKEN_ADDRESS_BYTES_LENTH * whitelistTokens.length + 2)); + function test_RevertWhen_HasTokenNotRegisteredBefore() public { + _prepareInputs(1); + whitelistTokens[0] = bytes32(bytes20(address(restakeToken))); + decimals[0] = 18; + tvlLimits[0] = 1e10 ether; + names[0] = "RestakeToken"; + metaData[0] = ""; vm.startPrank(exocoreValidatorSet.addr); - vm.expectRevert("ClientChainGateway: token should not be whitelisted before"); - clientGateway.addWhitelistTokens{value: nativeFee}(whitelistTokens); + vm.expectRevert("ExocoreGateway: token has not been added to whitelist before"); + exocoreGateway.updateWhitelistedTokens( + clientChainId, + whitelistTokens, + decimals, + tvlLimits, + names, + metaData + ); } - function test_SendMessage() public { - address[] memory whitelistTokens = new address[](1); - whitelistTokens[0] = address(restakeToken); - uint256 nativeFee = clientGateway.quote(new bytes(TOKEN_ADDRESS_BYTES_LENTH * whitelistTokens.length + 2)); + function test_Success_UpdateWhitelistTokens() public { + _prepareInputs(1); + whitelistTokens[0] = bytes32(bytes20(address(restakeToken))); + decimals[0] = 18; + tvlLimits[0] = 1e8 ether; + names[0] = "RestakeToken"; + metaData[0] = ""; + + // add token to whitelist first + _addWhitelistTokens(clientChainId, whitelistTokens, decimals, tvlLimits, names, metaData); + // then update token info + tvlLimits[0] = 1e10 ether; + vm.expectEmit(true, true, true, true, address(exocoreGateway)); + emit WhitelistTokenUpdated(clientChainId, whitelistTokens[0]); vm.startPrank(exocoreValidatorSet.addr); - vm.expectEmit(true, true, true, true, address(clientGateway)); - emit MessageSent(GatewayStorage.Action.REQUEST_REGISTER_TOKENS, generateUID(1, true), 1, nativeFee); - clientGateway.addWhitelistTokens{value: nativeFee}(whitelistTokens); + exocoreGateway.updateWhitelistedTokens( + clientChainId, + whitelistTokens, + decimals, + tvlLimits, + names, + metaData + ); } + function _prepareInputs(uint256 listLength) internal { + whitelistTokens = new bytes32[](listLength); + decimals = new uint8[](listLength); + tvlLimits = new uint256[](listLength); + names = new string[](listLength); + metaData = new string[](listLength); + } + + function _addWhitelistTokens( + uint32 clientChainId_, + bytes32[] memory whitelistTokens_, + uint8[] memory decimals_, + uint256[] memory tvlLimits_, + string[] memory names_, + string[] memory metaData_ + ) internal { + vm.startPrank(exocoreValidatorSet.addr); + uint messageLength = TOKEN_ADDRESS_BYTES_LENTH * whitelistTokens.length + 2; + uint256 nativeFee = exocoreGateway.quote(clientChainId, new bytes(messageLength)); + exocoreGateway.addWhitelistTokens{value: nativeFee}( + clientChainId_, + whitelistTokens_, + decimals_, + tvlLimits_, + names_, + metaData_ + ); + vm.stopPrank(); + } } \ No newline at end of file diff --git a/test/mocks/AssetsMock.sol b/test/mocks/AssetsMock.sol index f99fb8a0..30001ba9 100644 --- a/test/mocks/AssetsMock.sol +++ b/test/mocks/AssetsMock.sol @@ -50,24 +50,33 @@ contract AssetsMock is IAssets { return (true, chainIds); } - function registerClientChain(uint32 chainId) external returns (bool) { - require(!isRegisteredChain[chainId], "has already been registered"); - - isRegisteredChain[chainId] = true; - chainIds.push(chainId); + function registerClientChain( + uint32 clientChainId, + uint8 addressLength, + string calldata name, + string calldata metaInfo, + string calldata signatureType + ) external returns (bool) { + if (!isRegisteredChain[clientChainId]) { + isRegisteredChain[clientChainId] = true; + chainIds.push(clientChainId); + } return true; } - function registerTokens(uint32 chainId, bytes[] memory tokens) external returns (bool) { - require(isRegisteredChain[chainId], "the chain is not registered before"); - - for (uint256 i; i < tokens.length; i++) { - bytes memory token = tokens[i]; - require(token.length == 32, "token address with invalid length"); - require(!isRegisteredToken[chainId][token], "already registered token"); - - isRegisteredToken[chainId][token] = true; - } + function registerToken( + uint32 clientChainId, + bytes calldata token, + uint8 decimals, + uint256 tvlLimit, + string calldata name, + string calldata metaData + ) external returns (bool) { + require(isRegisteredChain[clientChainId], "the chain is not registered before"); + + if (!isRegisteredToken[clientChainId][token]) { + isRegisteredToken[clientChainId][token] = true; + } return true; } diff --git a/test/mocks/ExocoreGatewayMock.sol b/test/mocks/ExocoreGatewayMock.sol index 94842aa9..91be67cb 100644 --- a/test/mocks/ExocoreGatewayMock.sol +++ b/test/mocks/ExocoreGatewayMock.sol @@ -92,7 +92,6 @@ contract ExocoreGatewayMock is _whiteListFunctionSelectors[Action.REQUEST_WITHDRAW_PRINCIPAL_FROM_EXOCORE] = this.requestWithdrawPrincipal.selector; _whiteListFunctionSelectors[Action.REQUEST_WITHDRAW_REWARD_FROM_EXOCORE] = this.requestWithdrawReward.selector; - _whiteListFunctionSelectors[Action.REQUEST_REGISTER_TOKENS] = this.requestRegisterTokens.selector; } function pause() external { @@ -124,7 +123,7 @@ contract ExocoreGatewayMock is for (uint256 i = 0; i < clientChainIds.length; i++) { uint32 clientChainId = clientChainIds[i]; if (!chainToBootstrapped[clientChainId]) { - _sendInterchainMsg(clientChainId, Action.REQUEST_MARK_BOOTSTRAP, ""); + _sendInterchainMsg(clientChainId, Action.REQUEST_MARK_BOOTSTRAP, "", true); // TODO: should this be marked only upon receiving a response? chainToBootstrapped[clientChainId] = true; } @@ -136,30 +135,185 @@ contract ExocoreGatewayMock is * register the `cientChainId` to Exocore native module if the peer address is first time being set. * @param clientChainId The endpoint ID for client chain. * @param clientChainGateway The contract address to be associated with the corresponding endpoint. + * @param addressLength The bytes length of address type on that client chain + * @param name The name of client chain + * @param metaInfo The arbitrary metadata for client chain + * @param signatureType The cryptographic signature type that client chain supports * * @dev Only the owner/admin of the OApp can call this function. * @dev Indicates that the peer is trusted to send LayerZero messages to this OApp. * @dev Peer is a bytes32 to accommodate non-evm chains. */ + function registerOrUpdateClientChain( + uint32 clientChainId, + bytes32 clientChainGateway, + uint8 addressLength, + string calldata name, + string calldata metaInfo, + string calldata signatureType + ) + public + onlyOwner + whenNotPaused + { + _validatePeer(clientChainId, clientChainGateway); + _registerClientChain( + clientChainId, + addressLength, + name, + metaInfo, + signatureType + ); + super.setPeer(clientChainId, clientChainGateway); + + if (!isRegisteredClientChain[clientChainId]) { + isRegisteredClientChain[clientChainId] = true; + emit ClientChainRegistered(clientChainId); + } else { + emit ClientChainUpdated(clientChainId); + } + } + function setPeer(uint32 clientChainId, bytes32 clientChainGateway) public override(IOAppCore, OAppCoreUpgradeable) onlyOwner whenNotPaused { - _validatePeer(clientChainId, clientChainGateway); - _registerClientChain(clientChainId); + require(isRegisteredClientChain[clientChainId], "ExocoreGateway: client chain should be registered before setting peer to change peer address"); + super.setPeer(clientChainId, clientChainGateway); } + function addWhitelistTokens( + uint32 clientChainId, + bytes32[] calldata tokens, + uint8[] calldata decimals, + uint256[] calldata tvlLimits, + string[] calldata names, + string[] calldata metaData + ) external payable onlyOwner whenNotPaused { + _validateWhitelistTokensInput( + clientChainId, + tokens, + decimals, + tvlLimits, + names, + metaData + ); + + for (uint i; i < tokens.length; i++) { + require(tokens[i] != bytes32(0), "ExocoreGateway: token cannot be zero address"); + require(!isWhitelistedToken[tokens[i]], "ExocoreGateway: token has already been added to whitelist before"); + require(tvlLimits[i] >0, "ExocoreGateway: tvl limit should not be zero"); + + bool success = ASSETS_CONTRACT.registerToken( + clientChainId, + abi.encodePacked(tokens[i]), + decimals[i], + tvlLimits[i], + names[i], + metaData[i] + ); + + if (success) { + isWhitelistedToken[tokens[i]] = true; + } else { + revert AddWhitelistTokenFailed(tokens[i]); + } + + emit WhitelistTokenAdded(clientChainId, tokens[i]); + } + + _sendInterchainMsg(clientChainId, Action.REQUEST_ADD_WHITELIST_TOKENS, abi.encodePacked(uint8(tokens.length), tokens), false); + } + + function updateWhitelistedTokens( + uint32 clientChainId, + bytes32[] calldata tokens, + uint8[] calldata decimals, + uint256[] calldata tvlLimits, + string[] calldata names, + string[] calldata metaData + ) external onlyOwner whenNotPaused { + _validateWhitelistTokensInput( + clientChainId, + tokens, + decimals, + tvlLimits, + names, + metaData + ); + + for (uint i; i < tokens.length; i++) { + require(tokens[i] != bytes32(0), "ExocoreGateway: token cannot be zero address"); + require(isWhitelistedToken[tokens[i]], "ExocoreGateway: token has not been added to whitelist before"); + require(tvlLimits[i] >0, "ExocoreGateway: tvl limit should not be zero"); + + bool success = ASSETS_CONTRACT.registerToken( + clientChainId, + abi.encodePacked(tokens[i]), + decimals[i], + tvlLimits[i], + names[i], + metaData[i] + ); + + if (!success) { + revert UpdateWhitelistTokenFailed(tokens[i]); + } + + emit WhitelistTokenUpdated(clientChainId, tokens[i]); + } + } + + function _validateWhitelistTokensInput( + uint32 clientChainId, + bytes32[] calldata tokens, + uint8[] calldata decimals, + uint256[] calldata tvlLimits, + string[] calldata names, + string[] calldata metaData + ) internal view { + if (!isRegisteredClientChain[clientChainId]) { + revert ClientChainIDNotRegisteredBefore(clientChainId); + } + + uint256 expectedLength = tokens.length; + if (expectedLength > type(uint8).max) { + revert WhitelistTokensListTooLong(); + } + + if ( + decimals.length != expectedLength || + tvlLimits.length != expectedLength || + names.length != expectedLength || + metaData.length != expectedLength + ) { + revert InvalidWhitelistTokensInput(); + } + } + function _validatePeer(uint32 clientChainId, bytes32 clientChainGateway) internal pure { require(clientChainId != uint32(0), "ExocoreGateway: zero value is not invalid endpoint id"); require(clientChainGateway != bytes32(0), "ExocoreGateway: client chain gateway cannot be empty"); } - function _registerClientChain(uint32 clientChainId) internal { + function _registerClientChain( + uint32 clientChainId, + uint8 addressLength, + string calldata name, + string calldata metaInfo, + string calldata signatureType + ) internal { if (peers[clientChainId] == bytes32(0)) { - bool success = ASSETS_CONTRACT.registerClientChain(clientChainId); + bool success = ASSETS_CONTRACT.registerClientChain( + clientChainId, + addressLength, + name, + metaInfo, + signatureType + ); if (!success) { revert RegisterClientChainToExocoreFailed(clientChainId); } @@ -182,35 +336,11 @@ contract ExocoreGatewayMock is } } - function requestRegisterTokens(uint32 srcChainId, uint64 lzNonce, bytes calldata payload) - public - onlyCalledFromThis - { - uint8 count = uint8(payload[0]); - uint256 expectedLength = count * TOKEN_ADDRESS_BYTES_LENTH + 1; - _validatePayloadLength(payload, expectedLength, Action.REQUEST_DEPOSIT); - - bytes[] memory tokens = new bytes[](count); - for (uint256 i; i < count; i++) { - uint256 start = i * TOKEN_ADDRESS_BYTES_LENTH + 1; - uint256 end = start + TOKEN_ADDRESS_BYTES_LENTH; - tokens[i] = payload[start:end]; - } - - try ASSETS_CONTRACT.registerTokens(srcChainId, tokens) returns (bool success) { - _sendInterchainMsg(srcChainId, Action.RESPOND, abi.encodePacked(lzNonce, success)); - } catch { - emit ExocorePrecompileError(ASSETS_PRECOMPILE_ADDRESS, lzNonce); - - _sendInterchainMsg(srcChainId, Action.RESPOND, abi.encodePacked(lzNonce, false)); - } - } - function requestDeposit(uint32 srcChainId, uint64 lzNonce, bytes calldata payload) public onlyCalledFromThis { _validatePayloadLength(payload, DEPOSIT_REQUEST_LENGTH, Action.REQUEST_DEPOSIT); - bytes calldata token = payload[:32]; - bytes calldata depositor = payload[32:64]; + bytes memory token = payload[:32]; + bytes memory depositor = payload[32:64]; uint256 amount = uint256(bytes32(payload[64:96])); (bool success, uint256 updatedBalance) = ASSETS_CONTRACT.depositTo(srcChainId, token, depositor, amount); @@ -218,7 +348,7 @@ contract ExocoreGatewayMock is revert DepositRequestShouldNotFail(srcChainId, lzNonce); } - _sendInterchainMsg(srcChainId, Action.RESPOND, abi.encodePacked(lzNonce, success, updatedBalance)); + _sendInterchainMsg(srcChainId, Action.RESPOND, abi.encodePacked(lzNonce, success, updatedBalance), true); } function requestWithdrawPrincipal(uint32 srcChainId, uint64 lzNonce, bytes calldata payload) @@ -229,18 +359,18 @@ contract ExocoreGatewayMock is payload, WITHDRAW_PRINCIPAL_REQUEST_LENGTH, Action.REQUEST_WITHDRAW_PRINCIPAL_FROM_EXOCORE ); - bytes calldata token = payload[:32]; - bytes calldata withdrawer = payload[32:64]; + bytes memory token = payload[:32]; + bytes memory withdrawer = payload[32:64]; uint256 amount = uint256(bytes32(payload[64:96])); try ASSETS_CONTRACT.withdrawPrincipal(srcChainId, token, withdrawer, amount) returns ( bool success, uint256 updatedBalance ) { - _sendInterchainMsg(srcChainId, Action.RESPOND, abi.encodePacked(lzNonce, success, updatedBalance)); + _sendInterchainMsg(srcChainId, Action.RESPOND, abi.encodePacked(lzNonce, success, updatedBalance), true); } catch { emit ExocorePrecompileError(ASSETS_PRECOMPILE_ADDRESS, lzNonce); - _sendInterchainMsg(srcChainId, Action.RESPOND, abi.encodePacked(lzNonce, false, uint256(0))); + _sendInterchainMsg(srcChainId, Action.RESPOND, abi.encodePacked(lzNonce, false, uint256(0)), true); } } @@ -250,36 +380,36 @@ contract ExocoreGatewayMock is { _validatePayloadLength(payload, CLAIM_REWARD_REQUEST_LENGTH, Action.REQUEST_WITHDRAW_REWARD_FROM_EXOCORE); - bytes calldata token = payload[:32]; - bytes calldata withdrawer = payload[32:64]; + bytes memory token = payload[:32]; + bytes memory withdrawer = payload[32:64]; uint256 amount = uint256(bytes32(payload[64:96])); try CLAIM_REWARD_CONTRACT.claimReward(srcChainId, token, withdrawer, amount) returns ( bool success, uint256 updatedBalance ) { - _sendInterchainMsg(srcChainId, Action.RESPOND, abi.encodePacked(lzNonce, success, updatedBalance)); + _sendInterchainMsg(srcChainId, Action.RESPOND, abi.encodePacked(lzNonce, success, updatedBalance), true); } catch { emit ExocorePrecompileError(CLAIM_REWARD_PRECOMPILE_ADDRESS, lzNonce); - _sendInterchainMsg(srcChainId, Action.RESPOND, abi.encodePacked(lzNonce, false, uint256(0))); + _sendInterchainMsg(srcChainId, Action.RESPOND, abi.encodePacked(lzNonce, false, uint256(0)), true); } } function requestDelegateTo(uint32 srcChainId, uint64 lzNonce, bytes calldata payload) public onlyCalledFromThis { _validatePayloadLength(payload, DELEGATE_REQUEST_LENGTH, Action.REQUEST_DELEGATE_TO); - bytes calldata token = payload[:32]; - bytes calldata delegator = payload[32:64]; - bytes calldata operator = payload[64:106]; + bytes memory token = payload[:32]; + bytes memory delegator = payload[32:64]; + bytes memory operator = payload[64:106]; uint256 amount = uint256(bytes32(payload[106:138])); try DELEGATION_CONTRACT.delegateToThroughClientChain(srcChainId, lzNonce, token, delegator, operator, amount) returns (bool success) { - _sendInterchainMsg(srcChainId, Action.RESPOND, abi.encodePacked(lzNonce, success)); + _sendInterchainMsg(srcChainId, Action.RESPOND, abi.encodePacked(lzNonce, success), true); } catch { emit ExocorePrecompileError(DELEGATION_PRECOMPILE_ADDRESS, lzNonce); - _sendInterchainMsg(srcChainId, Action.RESPOND, abi.encodePacked(lzNonce, false)); + _sendInterchainMsg(srcChainId, Action.RESPOND, abi.encodePacked(lzNonce, false), true); } } @@ -297,11 +427,41 @@ contract ExocoreGatewayMock is try DELEGATION_CONTRACT.undelegateFromThroughClientChain( srcChainId, lzNonce, token, delegator, operator, amount ) returns (bool success) { - _sendInterchainMsg(srcChainId, Action.RESPOND, abi.encodePacked(lzNonce, success)); + _sendInterchainMsg(srcChainId, Action.RESPOND, abi.encodePacked(lzNonce, success), true); } catch { emit ExocorePrecompileError(DELEGATION_PRECOMPILE_ADDRESS, lzNonce); - _sendInterchainMsg(srcChainId, Action.RESPOND, abi.encodePacked(lzNonce, false)); + _sendInterchainMsg(srcChainId, Action.RESPOND, abi.encodePacked(lzNonce, false), true); + } + } + + function requestDepositThenDelegateTo(uint32 srcChainId, uint64 lzNonce, bytes calldata payload) + public + onlyCalledFromThis + { + _validatePayloadLength(payload, DEPOSIT_THEN_DELEGATE_REQUEST_LENGTH, Action.REQUEST_DEPOSIT_THEN_DELEGATE_TO); + + bytes memory token = payload[:32]; + bytes memory depositor = payload[32:64]; + bytes memory operator = payload[64:106]; + uint256 amount = uint256(bytes32(payload[106:138])); + + // while some of the code from requestDeposit and requestDelegateTo is duplicated here, + // it is done intentionally to work around Solidity's limitations with regards to + // function calls, error handling and indexing the return data of memory type. + // for example, you cannot index a bytes memory result from the requestDepositTo call, + // if you were to modify it to return bytes and then process them here. + + (bool success, uint256 updatedBalance) = ASSETS_CONTRACT.depositTo(srcChainId, token, depositor, amount); + if (!success) { + revert DepositRequestShouldNotFail(srcChainId, lzNonce); + } + try DELEGATION_CONTRACT.delegateToThroughClientChain(srcChainId, lzNonce, token, depositor, operator, amount) + returns (bool delegateSuccess) { + _sendInterchainMsg(srcChainId, Action.RESPOND, abi.encodePacked(lzNonce, delegateSuccess, updatedBalance), true); + } catch { + emit ExocorePrecompileError(DELEGATION_PRECOMPILE_ADDRESS, lzNonce); + _sendInterchainMsg(srcChainId, Action.RESPOND, abi.encodePacked(lzNonce, false, updatedBalance), true); } } @@ -311,7 +471,7 @@ contract ExocoreGatewayMock is } } - function _sendInterchainMsg(uint32 srcChainId, Action act, bytes memory actionArgs) internal whenNotPaused { + function _sendInterchainMsg(uint32 srcChainId, Action act, bytes memory actionArgs, bool payByApp) internal whenNotPaused { bytes memory payload = abi.encodePacked(act, actionArgs); bytes memory options = OptionsBuilder.newOptions().addExecutorLzReceiveOption( DESTINATION_GAS_LIMIT, DESTINATION_MSG_VALUE @@ -319,7 +479,7 @@ contract ExocoreGatewayMock is MessagingFee memory fee = _quote(srcChainId, payload, options, false); MessagingReceipt memory receipt = - _lzSend(srcChainId, payload, options, MessagingFee(fee.nativeFee, 0), exocoreValidatorSetAddress, true); + _lzSend(srcChainId, payload, options, MessagingFee(fee.nativeFee, 0), exocoreValidatorSetAddress, payByApp); emit MessageSent(act, receipt.guid, receipt.nonce, receipt.fee.nativeFee); } From 8d60ca4d761c21406896667b786c630857f5ed4f Mon Sep 17 00:00:00 2001 From: adu Date: Fri, 28 Jun 2024 10:13:55 +0800 Subject: [PATCH 06/19] fix format and lint --- script/3_Setup.s.sol | 24 +-- src/core/ClientChainGateway.sol | 6 +- src/core/ClientGatewayLzReceiver.sol | 1 + src/core/ExocoreGateway.sol | 89 +++------ src/core/NativeRestakingController.sol | 4 +- src/interfaces/IExocoreGateway.sol | 3 +- src/interfaces/precompiles/IAssets.sol | 3 +- src/interfaces/precompiles/IClaimReward.sol | 2 +- src/interfaces/precompiles/IDelegation.sol | 2 +- test/foundry/DepositThenDelegateTo.t.sol | 14 +- test/foundry/ExocoreDeployer.t.sol | 18 +- test/foundry/unit/ExocoreGateway.t.sol | 195 +++++--------------- test/mocks/AssetsMock.sol | 2 +- test/mocks/ExocoreGatewayMock.sol | 89 +++------ 14 files changed, 141 insertions(+), 311 deletions(-) diff --git a/script/3_Setup.s.sol b/script/3_Setup.s.sol index bdb636f6..92e9e364 100644 --- a/script/3_Setup.s.sol +++ b/script/3_Setup.s.sol @@ -65,7 +65,8 @@ contract SetupScript is BaseScript { vm.stopBroadcast(); // 2. setup Exocore contracts to make them ready for sending and receiving messages from client chain - // gateway, and register client chain meta data as well as well as adding tokens to whtielist to enable restaking + // gateway, and register client chain meta data as well as well as adding tokens to whtielist to enable + // restaking vm.selectFork(exocore); // Exocore validator set should be the owner of these contracts and only owner could setup contracts state vm.startBroadcast(exocoreValidatorSet.privateKey); @@ -75,16 +76,12 @@ contract SetupScript is BaseScript { address(clientGateway), address(clientChainLzEndpoint) ); } - // first register clientChainId to Exocore native module and set peer for client chain gateway to be ready for messaging + // first register clientChainId to Exocore native module and set peer for client chain gateway to be ready for + // messaging exocoreGateway.registerOrUpdateClientChain( - clientChainId, - address(clientGateway).toBytes32(), - 20, - "clientChain", - "", - "secp256k1" + clientChainId, address(clientGateway).toBytes32(), 20, "clientChain", "", "secp256k1" ); - // second add whitelist tokens and their meta data on Exocore side to enable LST Restaking and Native Restaking, + // second add whitelist tokens and their meta data on Exocore side to enable LST Restaking and Native Restaking, // and this would also add token addresses to client chain gateway's whitelist bytes32[] memory whitelistTokensBytes32 = new bytes32[](2); uint8[] memory decimals = new uint8[](2); @@ -104,15 +101,10 @@ contract SetupScript is BaseScript { names[1] = "RestakeToken"; metaData[1] = ""; - uint messageLength = TOKEN_ADDRESS_BYTES_LENTH * whitelistTokensBytes32.length + 2; + uint256 messageLength = TOKEN_ADDRESS_BYTES_LENTH * whitelistTokensBytes32.length + 2; uint256 nativeFee = exocoreGateway.quote(clientChainId, new bytes(messageLength)); exocoreGateway.addWhitelistTokens{value: nativeFee}( - clientChainId, - whitelistTokensBytes32, - decimals, - tvlLimits, - names, - metaData + clientChainId, whitelistTokensBytes32, decimals, tvlLimits, names, metaData ); vm.stopBroadcast(); } diff --git a/src/core/ClientChainGateway.sol b/src/core/ClientChainGateway.sol index e1c9feae..335ee8d0 100644 --- a/src/core/ClientChainGateway.sol +++ b/src/core/ClientChainGateway.sol @@ -10,7 +10,6 @@ import {ClientGatewayLzReceiver} from "./ClientGatewayLzReceiver.sol"; import {LSTRestakingController} from "./LSTRestakingController.sol"; import {NativeRestakingController} from "./NativeRestakingController.sol"; -import {ERC20} from "@openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; import {IOAppCore} from "@layerzero-v2/oapp/contracts/oapp/interfaces/IOAppCore.sol"; import {OptionsBuilder} from "@layerzero-v2/oapp/contracts/oapp/libs/OptionsBuilder.sol"; import {OwnableUpgradeable} from "@openzeppelin-upgradeable/contracts/access/OwnableUpgradeable.sol"; @@ -79,7 +78,8 @@ contract ClientChainGateway is _registeredResponseHooks[Action.REQUEST_DEPOSIT_THEN_DELEGATE_TO] = this.afterReceiveDepositThenDelegateToResponse.selector; - _whiteListFunctionSelectors[Action.REQUEST_ADD_WHITELIST_TOKENS] = this.afterReceiveAddWhitelistTokensRequest.selector; + _whiteListFunctionSelectors[Action.REQUEST_ADD_WHITELIST_TOKENS] = + this.afterReceiveAddWhitelistTokensRequest.selector; bootstrapped = true; @@ -121,7 +121,7 @@ contract ClientChainGateway is _unpause(); } - function addWhitelistTokens(address[] calldata tokens) external onlyOwner whenNotPaused { + function addWhitelistTokens(address[] calldata) external onlyOwner whenNotPaused { revert("this function is not supported for client chain, please register on Exocore"); } diff --git a/src/core/ClientGatewayLzReceiver.sol b/src/core/ClientGatewayLzReceiver.sol index 94c0a4ec..8ffad5db 100644 --- a/src/core/ClientGatewayLzReceiver.sol +++ b/src/core/ClientGatewayLzReceiver.sol @@ -212,4 +212,5 @@ abstract contract ClientGatewayLzReceiver is PausableUpgradeable, OAppReceiverUp } } } + } diff --git a/src/core/ExocoreGateway.sol b/src/core/ExocoreGateway.sol index eb59ea73..a672baca 100644 --- a/src/core/ExocoreGateway.sol +++ b/src/core/ExocoreGateway.sol @@ -127,25 +127,15 @@ contract ExocoreGateway is * @dev Peer is a bytes32 to accommodate non-evm chains. */ function registerOrUpdateClientChain( - uint32 clientChainId, + uint32 clientChainId, bytes32 clientChainGateway, uint8 addressLength, string calldata name, string calldata metaInfo, string calldata signatureType - ) - public - onlyOwner - whenNotPaused - { + ) public onlyOwner whenNotPaused { _validatePeer(clientChainId, clientChainGateway); - _registerClientChain( - clientChainId, - addressLength, - name, - metaInfo, - signatureType - ); + _registerClientChain(clientChainId, addressLength, name, metaInfo, signatureType); super.setPeer(clientChainId, clientChainGateway); if (!isRegisteredClientChain[clientChainId]) { @@ -162,7 +152,10 @@ contract ExocoreGateway is onlyOwner whenNotPaused { - require(isRegisteredClientChain[clientChainId], "ExocoreGateway: client chain should be registered before setting peer to change peer address"); + require( + isRegisteredClientChain[clientChainId], + "ExocoreGateway: client chain should be registered before setting peer to change peer address" + ); super.setPeer(clientChainId, clientChainGateway); } @@ -175,27 +168,15 @@ contract ExocoreGateway is string[] calldata names, string[] calldata metaData ) external payable onlyOwner whenNotPaused { - _validateWhitelistTokensInput( - clientChainId, - tokens, - decimals, - tvlLimits, - names, - metaData - ); + _validateWhitelistTokensInput(clientChainId, tokens, decimals, tvlLimits, names, metaData); - for (uint i; i < tokens.length; i++) { + for (uint256 i; i < tokens.length; i++) { require(tokens[i] != bytes32(0), "ExocoreGateway: token cannot be zero address"); require(!isWhitelistedToken[tokens[i]], "ExocoreGateway: token has already been added to whitelist before"); - require(tvlLimits[i] >0, "ExocoreGateway: tvl limit should not be zero"); + require(tvlLimits[i] > 0, "ExocoreGateway: tvl limit should not be zero"); bool success = ASSETS_CONTRACT.registerToken( - clientChainId, - abi.encodePacked(tokens[i]), - decimals[i], - tvlLimits[i], - names[i], - metaData[i] + clientChainId, abi.encodePacked(tokens[i]), decimals[i], tvlLimits[i], names[i], metaData[i] ); if (success) { @@ -207,7 +188,9 @@ contract ExocoreGateway is emit WhitelistTokenAdded(clientChainId, tokens[i]); } - _sendInterchainMsg(clientChainId, Action.REQUEST_ADD_WHITELIST_TOKENS, abi.encodePacked(uint8(tokens.length), tokens), false); + _sendInterchainMsg( + clientChainId, Action.REQUEST_ADD_WHITELIST_TOKENS, abi.encodePacked(uint8(tokens.length), tokens), false + ); } function updateWhitelistedTokens( @@ -218,27 +201,15 @@ contract ExocoreGateway is string[] calldata names, string[] calldata metaData ) external onlyOwner whenNotPaused { - _validateWhitelistTokensInput( - clientChainId, - tokens, - decimals, - tvlLimits, - names, - metaData - ); + _validateWhitelistTokensInput(clientChainId, tokens, decimals, tvlLimits, names, metaData); - for (uint i; i < tokens.length; i++) { + for (uint256 i; i < tokens.length; i++) { require(tokens[i] != bytes32(0), "ExocoreGateway: token cannot be zero address"); require(isWhitelistedToken[tokens[i]], "ExocoreGateway: token has not been added to whitelist before"); - require(tvlLimits[i] >0, "ExocoreGateway: tvl limit should not be zero"); + require(tvlLimits[i] > 0, "ExocoreGateway: tvl limit should not be zero"); bool success = ASSETS_CONTRACT.registerToken( - clientChainId, - abi.encodePacked(tokens[i]), - decimals[i], - tvlLimits[i], - names[i], - metaData[i] + clientChainId, abi.encodePacked(tokens[i]), decimals[i], tvlLimits[i], names[i], metaData[i] ); if (!success) { @@ -267,10 +238,8 @@ contract ExocoreGateway is } if ( - decimals.length != expectedLength || - tvlLimits.length != expectedLength || - names.length != expectedLength || - metaData.length != expectedLength + decimals.length != expectedLength || tvlLimits.length != expectedLength || names.length != expectedLength + || metaData.length != expectedLength ) { revert InvalidWhitelistTokensInput(); } @@ -289,13 +258,8 @@ contract ExocoreGateway is string calldata signatureType ) internal { if (peers[clientChainId] == bytes32(0)) { - bool success = ASSETS_CONTRACT.registerClientChain( - clientChainId, - addressLength, - name, - metaInfo, - signatureType - ); + bool success = + ASSETS_CONTRACT.registerClientChain(clientChainId, addressLength, name, metaInfo, signatureType); if (!success) { revert RegisterClientChainToExocoreFailed(clientChainId); } @@ -446,7 +410,9 @@ contract ExocoreGateway is } try DELEGATION_CONTRACT.delegateToThroughClientChain(srcChainId, lzNonce, token, depositor, operator, amount) returns (bool delegateSuccess) { - _sendInterchainMsg(srcChainId, Action.RESPOND, abi.encodePacked(lzNonce, delegateSuccess, updatedBalance), true); + _sendInterchainMsg( + srcChainId, Action.RESPOND, abi.encodePacked(lzNonce, delegateSuccess, updatedBalance), true + ); } catch { emit ExocorePrecompileError(DELEGATION_PRECOMPILE_ADDRESS, lzNonce); _sendInterchainMsg(srcChainId, Action.RESPOND, abi.encodePacked(lzNonce, false, updatedBalance), true); @@ -459,7 +425,10 @@ contract ExocoreGateway is } } - function _sendInterchainMsg(uint32 srcChainId, Action act, bytes memory actionArgs, bool payByApp) internal whenNotPaused { + function _sendInterchainMsg(uint32 srcChainId, Action act, bytes memory actionArgs, bool payByApp) + internal + whenNotPaused + { bytes memory payload = abi.encodePacked(act, actionArgs); bytes memory options = OptionsBuilder.newOptions().addExecutorLzReceiveOption( DESTINATION_GAS_LIMIT, DESTINATION_MSG_VALUE diff --git a/src/core/NativeRestakingController.sol b/src/core/NativeRestakingController.sol index 5c53a4e2..38e0563f 100644 --- a/src/core/NativeRestakingController.sol +++ b/src/core/NativeRestakingController.sol @@ -21,7 +21,9 @@ abstract contract NativeRestakingController is using ValidatorContainer for bytes32[]; modifier nativeRestakingEnabled() { - require(isWhitelistedToken[VIRTUAL_STAKED_ETH_ADDRESS], "NativeRestakingController: native restaking is not enabled"); + require( + isWhitelistedToken[VIRTUAL_STAKED_ETH_ADDRESS], "NativeRestakingController: native restaking is not enabled" + ); _; } diff --git a/src/interfaces/IExocoreGateway.sol b/src/interfaces/IExocoreGateway.sol index b5ebc875..930b683c 100644 --- a/src/interfaces/IExocoreGateway.sol +++ b/src/interfaces/IExocoreGateway.sol @@ -8,7 +8,7 @@ interface IExocoreGateway is IOAppReceiver, IOAppCore { function quote(uint32 srcChainid, bytes memory _message) external view returns (uint256 nativeFee); function registerOrUpdateClientChain( - uint32 clientChainId, + uint32 clientChainId, bytes32 clientChainGateway, uint8 addressLength, string calldata name, @@ -24,4 +24,5 @@ interface IExocoreGateway is IOAppReceiver, IOAppCore { string[] calldata names, string[] calldata metaData ) external payable; + } diff --git a/src/interfaces/precompiles/IAssets.sol b/src/interfaces/precompiles/IAssets.sol index 499dc69b..3aa4b8de 100644 --- a/src/interfaces/precompiles/IAssets.sol +++ b/src/interfaces/precompiles/IAssets.sol @@ -70,4 +70,5 @@ interface IAssets { string calldata name, string calldata metaData ) external returns (bool success); -} \ No newline at end of file + +} diff --git a/src/interfaces/precompiles/IClaimReward.sol b/src/interfaces/precompiles/IClaimReward.sol index 68acd483..b8b59944 100644 --- a/src/interfaces/precompiles/IClaimReward.sol +++ b/src/interfaces/precompiles/IClaimReward.sol @@ -29,4 +29,4 @@ interface IClaimReward { uint256 opAmount ) external returns (bool success, uint256 latestAssetState); -} \ No newline at end of file +} diff --git a/src/interfaces/precompiles/IDelegation.sol b/src/interfaces/precompiles/IDelegation.sol index 9cefd37e..cb63808d 100644 --- a/src/interfaces/precompiles/IDelegation.sol +++ b/src/interfaces/precompiles/IDelegation.sol @@ -51,4 +51,4 @@ interface IDelegation { uint256 opAmount ) external returns (bool success); -} \ No newline at end of file +} diff --git a/test/foundry/DepositThenDelegateTo.t.sol b/test/foundry/DepositThenDelegateTo.t.sol index bd2bc35d..764c109c 100644 --- a/test/foundry/DepositThenDelegateTo.t.sol +++ b/test/foundry/DepositThenDelegateTo.t.sol @@ -67,7 +67,16 @@ contract DepositThenDelegateToTest is ExocoreDeployer { (bytes32 requestId, bytes memory requestPayload) = _testRequest(delegator, operatorAddress, requestLzNonce, delegateAmount); - _testResponse(requestId, requestPayload, delegator, relayer, operatorAddress, requestLzNonce, responseLzNonce, delegateAmount); + _testResponse( + requestId, + requestPayload, + delegator, + relayer, + operatorAddress, + requestLzNonce, + responseLzNonce, + delegateAmount + ); } function _testRequest(address delegator, string memory operatorAddress, uint64 lzNonce, uint256 delegateAmount) @@ -123,7 +132,8 @@ contract DepositThenDelegateToTest is ExocoreDeployer { uint64 responseLzNonce, uint256 delegateAmount ) private { - bytes memory responsePayload = abi.encodePacked(GatewayStorage.Action.RESPOND, requestLzNonce, true, delegateAmount); + bytes memory responsePayload = + abi.encodePacked(GatewayStorage.Action.RESPOND, requestLzNonce, true, delegateAmount); uint256 responseNativeFee = exocoreGateway.quote(clientChainId, responsePayload); bytes32 responseId = generateUID(responseLzNonce, false); diff --git a/test/foundry/ExocoreDeployer.t.sol b/test/foundry/ExocoreDeployer.t.sol index ab5b8838..1b0e5bf4 100644 --- a/test/foundry/ExocoreDeployer.t.sol +++ b/test/foundry/ExocoreDeployer.t.sol @@ -147,8 +147,8 @@ contract ExocoreDeployer is Test { // estimate l0 relay fee that the user should pay bytes memory registerTokensRequestPayload = abi.encodePacked( - GatewayStorage.Action.REQUEST_ADD_WHITELIST_TOKENS, - uint8(whitelistTokens.length), + GatewayStorage.Action.REQUEST_ADD_WHITELIST_TOKENS, + uint8(whitelistTokens.length), bytes32(bytes20(address(restakeToken))), bytes32(bytes20(VIRTUAL_STAKED_ETH_ADDRESS)) ); @@ -173,12 +173,7 @@ contract ExocoreDeployer is Test { registerTokensRequestNativeFee ); exocoreGateway.addWhitelistTokens{value: registerTokensRequestNativeFee}( - clientChainId, - whitelistTokens, - decimals, - tvlLimits, - names, - metaData + clientChainId, whitelistTokens, decimals, tvlLimits, names, metaData ); // second layerzero relayers should watch the request message packet and relay the message to destination @@ -318,12 +313,7 @@ contract ExocoreDeployer is Test { // messages. On Exocore side, this is done by calling registerClientChain clientGateway.setPeer(exocoreChainId, address(exocoreGateway).toBytes32()); exocoreGateway.registerOrUpdateClientChain( - clientChainId, - address(clientGateway).toBytes32(), - 20, - "clientChain", - "", - "secp256k1" + clientChainId, address(clientGateway).toBytes32(), 20, "clientChain", "", "secp256k1" ); vm.stopPrank(); } diff --git a/test/foundry/unit/ExocoreGateway.t.sol b/test/foundry/unit/ExocoreGateway.t.sol index f67cf347..69c63688 100644 --- a/test/foundry/unit/ExocoreGateway.t.sol +++ b/test/foundry/unit/ExocoreGateway.t.sol @@ -18,8 +18,8 @@ import "src/core/ClientChainGateway.sol"; import "src/core/ExocoreGateway.sol"; import {Vault} from "src/core/Vault.sol"; -import {GatewayStorage} from "src/storage/GatewayStorage.sol"; import {ExocoreGatewayStorage} from "src/storage/ExocoreGatewayStorage.sol"; +import {GatewayStorage} from "src/storage/GatewayStorage.sol"; contract SetUp is Test { @@ -97,12 +97,7 @@ contract SetUp is Test { vm.startPrank(exocoreValidatorSet.addr); exocoreLzEndpoint.setDestLzEndpoint(address(clientGateway), address(clientLzEndpoint)); exocoreGateway.registerOrUpdateClientChain( - clientChainId, - address(clientGateway).toBytes32(), - 20, - "clientChain", - "", - "secp256k1" + clientChainId, address(clientGateway).toBytes32(), 20, "clientChain", "", "secp256k1" ); vm.stopPrank(); @@ -199,7 +194,7 @@ contract AddWhitelistTokens is SetUp { uint256 internal constant TOKEN_ADDRESS_BYTES_LENTH = 32; event WhitelistTokenAdded(uint32 clientChainId, bytes32 token); - + bytes32[] whitelistTokens; uint8[] decimals; uint256[] tvlLimits; @@ -209,18 +204,13 @@ contract AddWhitelistTokens is SetUp { function test_RevertWhen_CallerNotOwner() public { _prepareInputs(2); - uint messageLength = TOKEN_ADDRESS_BYTES_LENTH * whitelistTokens.length + 2; + uint256 messageLength = TOKEN_ADDRESS_BYTES_LENTH * whitelistTokens.length + 2; uint256 nativeFee = exocoreGateway.quote(clientChainId, new bytes(messageLength)); vm.startPrank(deployer.addr); vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, deployer.addr)); exocoreGateway.addWhitelistTokens{value: nativeFee}( - clientChainId, - whitelistTokens, - decimals, - tvlLimits, - names, - metaData + clientChainId, whitelistTokens, decimals, tvlLimits, names, metaData ); } @@ -230,16 +220,11 @@ contract AddWhitelistTokens is SetUp { _prepareInputs(2); - uint messageLength = TOKEN_ADDRESS_BYTES_LENTH * whitelistTokens.length + 2; + uint256 messageLength = TOKEN_ADDRESS_BYTES_LENTH * whitelistTokens.length + 2; uint256 nativeFee = exocoreGateway.quote(clientChainId, new bytes(messageLength)); vm.expectRevert(PausableUpgradeable.EnforcedPause.selector); exocoreGateway.addWhitelistTokens{value: nativeFee}( - clientChainId, - whitelistTokens, - decimals, - tvlLimits, - names, - metaData + clientChainId, whitelistTokens, decimals, tvlLimits, names, metaData ); } @@ -247,36 +232,28 @@ contract AddWhitelistTokens is SetUp { uint32 anotherClientChain = 3; _prepareInputs(2); - uint messageLength = TOKEN_ADDRESS_BYTES_LENTH * whitelistTokens.length + 2; + uint256 messageLength = TOKEN_ADDRESS_BYTES_LENTH * whitelistTokens.length + 2; uint256 nativeFee = exocoreGateway.quote(clientChainId, new bytes(messageLength)); vm.startPrank(exocoreValidatorSet.addr); - vm.expectRevert(abi.encodeWithSelector(ExocoreGatewayStorage.ClientChainIDNotRegisteredBefore.selector, anotherClientChain)); + vm.expectRevert( + abi.encodeWithSelector(ExocoreGatewayStorage.ClientChainIDNotRegisteredBefore.selector, anotherClientChain) + ); exocoreGateway.addWhitelistTokens{value: nativeFee}( - anotherClientChain, - whitelistTokens, - decimals, - tvlLimits, - names, - metaData + anotherClientChain, whitelistTokens, decimals, tvlLimits, names, metaData ); } function test_RevertWhen_TokensListTooLong() public { _prepareInputs(256); - uint messageLength = TOKEN_ADDRESS_BYTES_LENTH * whitelistTokens.length + 2; + uint256 messageLength = TOKEN_ADDRESS_BYTES_LENTH * whitelistTokens.length + 2; uint256 nativeFee = exocoreGateway.quote(clientChainId, new bytes(messageLength)); vm.startPrank(exocoreValidatorSet.addr); vm.expectRevert(abi.encodeWithSelector(ExocoreGatewayStorage.WhitelistTokensListTooLong.selector)); exocoreGateway.addWhitelistTokens{value: nativeFee}( - clientChainId, - whitelistTokens, - decimals, - tvlLimits, - names, - metaData + clientChainId, whitelistTokens, decimals, tvlLimits, names, metaData ); } @@ -284,18 +261,13 @@ contract AddWhitelistTokens is SetUp { _prepareInputs(2); decimals.push(18); - uint messageLength = TOKEN_ADDRESS_BYTES_LENTH * whitelistTokens.length + 2; + uint256 messageLength = TOKEN_ADDRESS_BYTES_LENTH * whitelistTokens.length + 2; uint256 nativeFee = exocoreGateway.quote(clientChainId, new bytes(messageLength)); vm.startPrank(exocoreValidatorSet.addr); vm.expectRevert(abi.encodeWithSelector(ExocoreGatewayStorage.InvalidWhitelistTokensInput.selector)); exocoreGateway.addWhitelistTokens{value: nativeFee}( - clientChainId, - whitelistTokens, - decimals, - tvlLimits, - names, - metaData + clientChainId, whitelistTokens, decimals, tvlLimits, names, metaData ); } @@ -305,18 +277,13 @@ contract AddWhitelistTokens is SetUp { tvlLimits[0] = 1e8 ether; tvlLimits[1] = 1e8 ether; - uint messageLength = TOKEN_ADDRESS_BYTES_LENTH * whitelistTokens.length + 2; + uint256 messageLength = TOKEN_ADDRESS_BYTES_LENTH * whitelistTokens.length + 2; uint256 nativeFee = exocoreGateway.quote(clientChainId, new bytes(messageLength)); vm.startPrank(exocoreValidatorSet.addr); vm.expectRevert("ExocoreGateway: token cannot be zero address"); exocoreGateway.addWhitelistTokens{value: nativeFee}( - clientChainId, - whitelistTokens, - decimals, - tvlLimits, - names, - metaData + clientChainId, whitelistTokens, decimals, tvlLimits, names, metaData ); } @@ -324,18 +291,13 @@ contract AddWhitelistTokens is SetUp { _prepareInputs(1); whitelistTokens[0] = bytes32(bytes20(address(restakeToken))); - uint messageLength = TOKEN_ADDRESS_BYTES_LENTH * whitelistTokens.length + 2; + uint256 messageLength = TOKEN_ADDRESS_BYTES_LENTH * whitelistTokens.length + 2; uint256 nativeFee = exocoreGateway.quote(clientChainId, new bytes(messageLength)); vm.startPrank(exocoreValidatorSet.addr); vm.expectRevert("ExocoreGateway: tvl limit should not be zero"); exocoreGateway.addWhitelistTokens{value: nativeFee}( - clientChainId, - whitelistTokens, - decimals, - tvlLimits, - names, - metaData + clientChainId, whitelistTokens, decimals, tvlLimits, names, metaData ); } @@ -347,7 +309,7 @@ contract AddWhitelistTokens is SetUp { names[0] = "RestakeToken"; metaData[0] = ""; - uint messageLength = TOKEN_ADDRESS_BYTES_LENTH * whitelistTokens.length + 2; + uint256 messageLength = TOKEN_ADDRESS_BYTES_LENTH * whitelistTokens.length + 2; uint256 nativeFee = exocoreGateway.quote(clientChainId, new bytes(messageLength)); vm.startPrank(exocoreValidatorSet.addr); @@ -355,12 +317,7 @@ contract AddWhitelistTokens is SetUp { emit WhitelistTokenAdded(clientChainId, whitelistTokens[0]); emit MessageSent(GatewayStorage.Action.REQUEST_ADD_WHITELIST_TOKENS, generateUID(1, false), 1, nativeFee); exocoreGateway.addWhitelistTokens{value: nativeFee}( - clientChainId, - whitelistTokens, - decimals, - tvlLimits, - names, - metaData + clientChainId, whitelistTokens, decimals, tvlLimits, names, metaData ); } @@ -383,6 +340,7 @@ contract AddWhitelistTokens is SetUp { ); } } + } contract UpdateWhitelistTokens is SetUp { @@ -392,7 +350,7 @@ contract UpdateWhitelistTokens is SetUp { uint256 internal constant TOKEN_ADDRESS_BYTES_LENTH = 32; event WhitelistTokenUpdated(uint32 clientChainId, bytes32 token); - + bytes32[] whitelistTokens; uint8[] decimals; uint256[] tvlLimits; @@ -404,14 +362,7 @@ contract UpdateWhitelistTokens is SetUp { vm.startPrank(deployer.addr); vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, deployer.addr)); - exocoreGateway.updateWhitelistedTokens( - clientChainId, - whitelistTokens, - decimals, - tvlLimits, - names, - metaData - ); + exocoreGateway.updateWhitelistedTokens(clientChainId, whitelistTokens, decimals, tvlLimits, names, metaData); } function test_RevertWhen_Paused() public { @@ -421,14 +372,7 @@ contract UpdateWhitelistTokens is SetUp { _prepareInputs(2); vm.expectRevert(PausableUpgradeable.EnforcedPause.selector); - exocoreGateway.updateWhitelistedTokens( - clientChainId, - whitelistTokens, - decimals, - tvlLimits, - names, - metaData - ); + exocoreGateway.updateWhitelistedTokens(clientChainId, whitelistTokens, decimals, tvlLimits, names, metaData); } function test_RevertWhen_ClientChainNotRegisteredBefore() public { @@ -436,14 +380,11 @@ contract UpdateWhitelistTokens is SetUp { _prepareInputs(2); vm.startPrank(exocoreValidatorSet.addr); - vm.expectRevert(abi.encodeWithSelector(ExocoreGatewayStorage.ClientChainIDNotRegisteredBefore.selector, anotherClientChain)); + vm.expectRevert( + abi.encodeWithSelector(ExocoreGatewayStorage.ClientChainIDNotRegisteredBefore.selector, anotherClientChain) + ); exocoreGateway.updateWhitelistedTokens( - anotherClientChain, - whitelistTokens, - decimals, - tvlLimits, - names, - metaData + anotherClientChain, whitelistTokens, decimals, tvlLimits, names, metaData ); } @@ -452,14 +393,7 @@ contract UpdateWhitelistTokens is SetUp { vm.startPrank(exocoreValidatorSet.addr); vm.expectRevert(abi.encodeWithSelector(ExocoreGatewayStorage.WhitelistTokensListTooLong.selector)); - exocoreGateway.updateWhitelistedTokens( - clientChainId, - whitelistTokens, - decimals, - tvlLimits, - names, - metaData - ); + exocoreGateway.updateWhitelistedTokens(clientChainId, whitelistTokens, decimals, tvlLimits, names, metaData); } function test_RevertWhen_LengthNotMatch() public { @@ -468,14 +402,7 @@ contract UpdateWhitelistTokens is SetUp { vm.startPrank(exocoreValidatorSet.addr); vm.expectRevert(abi.encodeWithSelector(ExocoreGatewayStorage.InvalidWhitelistTokensInput.selector)); - exocoreGateway.updateWhitelistedTokens( - clientChainId, - whitelistTokens, - decimals, - tvlLimits, - names, - metaData - ); + exocoreGateway.updateWhitelistedTokens(clientChainId, whitelistTokens, decimals, tvlLimits, names, metaData); } function test_RevertWhen_HasZeroAddressToken() public { @@ -494,14 +421,7 @@ contract UpdateWhitelistTokens is SetUp { vm.startPrank(exocoreValidatorSet.addr); vm.expectRevert("ExocoreGateway: token cannot be zero address"); - exocoreGateway.updateWhitelistedTokens( - clientChainId, - whitelistTokens, - decimals, - tvlLimits, - names, - metaData - ); + exocoreGateway.updateWhitelistedTokens(clientChainId, whitelistTokens, decimals, tvlLimits, names, metaData); } function test_RevertWhen_HasZeroTVMLimit() public { @@ -517,14 +437,7 @@ contract UpdateWhitelistTokens is SetUp { vm.startPrank(exocoreValidatorSet.addr); vm.expectRevert("ExocoreGateway: tvl limit should not be zero"); - exocoreGateway.updateWhitelistedTokens( - clientChainId, - whitelistTokens, - decimals, - tvlLimits, - names, - metaData - ); + exocoreGateway.updateWhitelistedTokens(clientChainId, whitelistTokens, decimals, tvlLimits, names, metaData); } function test_RevertWhen_HasTokenNotRegisteredBefore() public { @@ -537,14 +450,7 @@ contract UpdateWhitelistTokens is SetUp { vm.startPrank(exocoreValidatorSet.addr); vm.expectRevert("ExocoreGateway: token has not been added to whitelist before"); - exocoreGateway.updateWhitelistedTokens( - clientChainId, - whitelistTokens, - decimals, - tvlLimits, - names, - metaData - ); + exocoreGateway.updateWhitelistedTokens(clientChainId, whitelistTokens, decimals, tvlLimits, names, metaData); } function test_Success_UpdateWhitelistTokens() public { @@ -563,14 +469,7 @@ contract UpdateWhitelistTokens is SetUp { vm.expectEmit(true, true, true, true, address(exocoreGateway)); emit WhitelistTokenUpdated(clientChainId, whitelistTokens[0]); vm.startPrank(exocoreValidatorSet.addr); - exocoreGateway.updateWhitelistedTokens( - clientChainId, - whitelistTokens, - decimals, - tvlLimits, - names, - metaData - ); + exocoreGateway.updateWhitelistedTokens(clientChainId, whitelistTokens, decimals, tvlLimits, names, metaData); } function _prepareInputs(uint256 listLength) internal { @@ -582,24 +481,20 @@ contract UpdateWhitelistTokens is SetUp { } function _addWhitelistTokens( - uint32 clientChainId_, - bytes32[] memory whitelistTokens_, - uint8[] memory decimals_, - uint256[] memory tvlLimits_, - string[] memory names_, + uint32 clientChainId_, + bytes32[] memory whitelistTokens_, + uint8[] memory decimals_, + uint256[] memory tvlLimits_, + string[] memory names_, string[] memory metaData_ ) internal { vm.startPrank(exocoreValidatorSet.addr); - uint messageLength = TOKEN_ADDRESS_BYTES_LENTH * whitelistTokens.length + 2; + uint256 messageLength = TOKEN_ADDRESS_BYTES_LENTH * whitelistTokens.length + 2; uint256 nativeFee = exocoreGateway.quote(clientChainId, new bytes(messageLength)); exocoreGateway.addWhitelistTokens{value: nativeFee}( - clientChainId_, - whitelistTokens_, - decimals_, - tvlLimits_, - names_, - metaData_ + clientChainId_, whitelistTokens_, decimals_, tvlLimits_, names_, metaData_ ); vm.stopPrank(); } -} \ No newline at end of file + +} diff --git a/test/mocks/AssetsMock.sol b/test/mocks/AssetsMock.sol index 30001ba9..99443089 100644 --- a/test/mocks/AssetsMock.sol +++ b/test/mocks/AssetsMock.sol @@ -76,7 +76,7 @@ contract AssetsMock is IAssets { if (!isRegisteredToken[clientChainId][token]) { isRegisteredToken[clientChainId][token] = true; - } + } return true; } diff --git a/test/mocks/ExocoreGatewayMock.sol b/test/mocks/ExocoreGatewayMock.sol index 91be67cb..187274a6 100644 --- a/test/mocks/ExocoreGatewayMock.sol +++ b/test/mocks/ExocoreGatewayMock.sol @@ -145,25 +145,15 @@ contract ExocoreGatewayMock is * @dev Peer is a bytes32 to accommodate non-evm chains. */ function registerOrUpdateClientChain( - uint32 clientChainId, + uint32 clientChainId, bytes32 clientChainGateway, uint8 addressLength, string calldata name, string calldata metaInfo, string calldata signatureType - ) - public - onlyOwner - whenNotPaused - { + ) public onlyOwner whenNotPaused { _validatePeer(clientChainId, clientChainGateway); - _registerClientChain( - clientChainId, - addressLength, - name, - metaInfo, - signatureType - ); + _registerClientChain(clientChainId, addressLength, name, metaInfo, signatureType); super.setPeer(clientChainId, clientChainGateway); if (!isRegisteredClientChain[clientChainId]) { @@ -180,7 +170,10 @@ contract ExocoreGatewayMock is onlyOwner whenNotPaused { - require(isRegisteredClientChain[clientChainId], "ExocoreGateway: client chain should be registered before setting peer to change peer address"); + require( + isRegisteredClientChain[clientChainId], + "ExocoreGateway: client chain should be registered before setting peer to change peer address" + ); super.setPeer(clientChainId, clientChainGateway); } @@ -193,27 +186,15 @@ contract ExocoreGatewayMock is string[] calldata names, string[] calldata metaData ) external payable onlyOwner whenNotPaused { - _validateWhitelistTokensInput( - clientChainId, - tokens, - decimals, - tvlLimits, - names, - metaData - ); + _validateWhitelistTokensInput(clientChainId, tokens, decimals, tvlLimits, names, metaData); - for (uint i; i < tokens.length; i++) { + for (uint256 i; i < tokens.length; i++) { require(tokens[i] != bytes32(0), "ExocoreGateway: token cannot be zero address"); require(!isWhitelistedToken[tokens[i]], "ExocoreGateway: token has already been added to whitelist before"); - require(tvlLimits[i] >0, "ExocoreGateway: tvl limit should not be zero"); + require(tvlLimits[i] > 0, "ExocoreGateway: tvl limit should not be zero"); bool success = ASSETS_CONTRACT.registerToken( - clientChainId, - abi.encodePacked(tokens[i]), - decimals[i], - tvlLimits[i], - names[i], - metaData[i] + clientChainId, abi.encodePacked(tokens[i]), decimals[i], tvlLimits[i], names[i], metaData[i] ); if (success) { @@ -225,7 +206,9 @@ contract ExocoreGatewayMock is emit WhitelistTokenAdded(clientChainId, tokens[i]); } - _sendInterchainMsg(clientChainId, Action.REQUEST_ADD_WHITELIST_TOKENS, abi.encodePacked(uint8(tokens.length), tokens), false); + _sendInterchainMsg( + clientChainId, Action.REQUEST_ADD_WHITELIST_TOKENS, abi.encodePacked(uint8(tokens.length), tokens), false + ); } function updateWhitelistedTokens( @@ -236,27 +219,15 @@ contract ExocoreGatewayMock is string[] calldata names, string[] calldata metaData ) external onlyOwner whenNotPaused { - _validateWhitelistTokensInput( - clientChainId, - tokens, - decimals, - tvlLimits, - names, - metaData - ); + _validateWhitelistTokensInput(clientChainId, tokens, decimals, tvlLimits, names, metaData); - for (uint i; i < tokens.length; i++) { + for (uint256 i; i < tokens.length; i++) { require(tokens[i] != bytes32(0), "ExocoreGateway: token cannot be zero address"); require(isWhitelistedToken[tokens[i]], "ExocoreGateway: token has not been added to whitelist before"); - require(tvlLimits[i] >0, "ExocoreGateway: tvl limit should not be zero"); + require(tvlLimits[i] > 0, "ExocoreGateway: tvl limit should not be zero"); bool success = ASSETS_CONTRACT.registerToken( - clientChainId, - abi.encodePacked(tokens[i]), - decimals[i], - tvlLimits[i], - names[i], - metaData[i] + clientChainId, abi.encodePacked(tokens[i]), decimals[i], tvlLimits[i], names[i], metaData[i] ); if (!success) { @@ -285,10 +256,8 @@ contract ExocoreGatewayMock is } if ( - decimals.length != expectedLength || - tvlLimits.length != expectedLength || - names.length != expectedLength || - metaData.length != expectedLength + decimals.length != expectedLength || tvlLimits.length != expectedLength || names.length != expectedLength + || metaData.length != expectedLength ) { revert InvalidWhitelistTokensInput(); } @@ -307,13 +276,8 @@ contract ExocoreGatewayMock is string calldata signatureType ) internal { if (peers[clientChainId] == bytes32(0)) { - bool success = ASSETS_CONTRACT.registerClientChain( - clientChainId, - addressLength, - name, - metaInfo, - signatureType - ); + bool success = + ASSETS_CONTRACT.registerClientChain(clientChainId, addressLength, name, metaInfo, signatureType); if (!success) { revert RegisterClientChainToExocoreFailed(clientChainId); } @@ -458,7 +422,9 @@ contract ExocoreGatewayMock is } try DELEGATION_CONTRACT.delegateToThroughClientChain(srcChainId, lzNonce, token, depositor, operator, amount) returns (bool delegateSuccess) { - _sendInterchainMsg(srcChainId, Action.RESPOND, abi.encodePacked(lzNonce, delegateSuccess, updatedBalance), true); + _sendInterchainMsg( + srcChainId, Action.RESPOND, abi.encodePacked(lzNonce, delegateSuccess, updatedBalance), true + ); } catch { emit ExocorePrecompileError(DELEGATION_PRECOMPILE_ADDRESS, lzNonce); _sendInterchainMsg(srcChainId, Action.RESPOND, abi.encodePacked(lzNonce, false, updatedBalance), true); @@ -471,7 +437,10 @@ contract ExocoreGatewayMock is } } - function _sendInterchainMsg(uint32 srcChainId, Action act, bytes memory actionArgs, bool payByApp) internal whenNotPaused { + function _sendInterchainMsg(uint32 srcChainId, Action act, bytes memory actionArgs, bool payByApp) + internal + whenNotPaused + { bytes memory payload = abi.encodePacked(act, actionArgs); bytes memory options = OptionsBuilder.newOptions().addExecutorLzReceiveOption( DESTINATION_GAS_LIMIT, DESTINATION_MSG_VALUE From 7ad92c3561f856f63af31e7ca5b7cebb27a55462 Mon Sep 17 00:00:00 2001 From: adu Date: Mon, 1 Jul 2024 16:56:31 +0800 Subject: [PATCH 07/19] fix(typo): TOKEN_ADDRESS_BYTES_LENTH => TOKEN_ADDRESS_BYTES_LENGTH --- script/3_Setup.s.sol | 2 +- script/BaseScript.sol | 2 +- src/core/ClientGatewayLzReceiver.sol | 6 +++--- src/storage/ClientChainGatewayStorage.sol | 2 +- src/storage/ExocoreGatewayStorage.sol | 2 +- test/foundry/unit/ExocoreGateway.t.sol | 22 +++++++++++----------- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/script/3_Setup.s.sol b/script/3_Setup.s.sol index 92e9e364..2284f3c0 100644 --- a/script/3_Setup.s.sol +++ b/script/3_Setup.s.sol @@ -101,7 +101,7 @@ contract SetupScript is BaseScript { names[1] = "RestakeToken"; metaData[1] = ""; - uint256 messageLength = TOKEN_ADDRESS_BYTES_LENTH * whitelistTokensBytes32.length + 2; + uint256 messageLength = TOKEN_ADDRESS_BYTES_LENGTH * whitelistTokensBytes32.length + 2; uint256 nativeFee = exocoreGateway.quote(clientChainId, new bytes(messageLength)); exocoreGateway.addWhitelistTokens{value: nativeFee}( clientChainId, whitelistTokensBytes32, decimals, tvlLimits, names, metaData diff --git a/script/BaseScript.sol b/script/BaseScript.sol index abaeefc1..8a82a90e 100644 --- a/script/BaseScript.sol +++ b/script/BaseScript.sol @@ -70,7 +70,7 @@ contract BaseScript is Script { uint256 constant DEPOSIT_AMOUNT = 1 ether; uint256 constant WITHDRAW_AMOUNT = 1 ether; address internal constant VIRTUAL_STAKED_ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; - uint256 internal constant TOKEN_ADDRESS_BYTES_LENTH = 32; + uint256 internal constant TOKEN_ADDRESS_BYTES_LENGTH = 32; bool useExocorePrecompileMock; bool useEndpointMock; diff --git a/src/core/ClientGatewayLzReceiver.sol b/src/core/ClientGatewayLzReceiver.sol index 8ffad5db..b0d9d6b8 100644 --- a/src/core/ClientGatewayLzReceiver.sol +++ b/src/core/ClientGatewayLzReceiver.sol @@ -189,14 +189,14 @@ abstract contract ClientGatewayLzReceiver is PausableUpgradeable, OAppReceiverUp whenNotPaused { uint8 count = uint8(requestPayload[0]); - uint256 expectedLength = count * TOKEN_ADDRESS_BYTES_LENTH + 1; + uint256 expectedLength = count * TOKEN_ADDRESS_BYTES_LENGTH + 1; if (requestPayload.length != expectedLength) { revert InvalidAddWhitelistTokensRequest(expectedLength, requestPayload.length); } for (uint256 i; i < count; i++) { - uint256 start = i * TOKEN_ADDRESS_BYTES_LENTH + 1; - uint256 end = start + TOKEN_ADDRESS_BYTES_LENTH; + uint256 start = i * TOKEN_ADDRESS_BYTES_LENGTH + 1; + uint256 end = start + TOKEN_ADDRESS_BYTES_LENGTH; address token = address(bytes20(requestPayload[start:end])); if (!isWhitelistedToken[token]) { diff --git a/src/storage/ClientChainGatewayStorage.sol b/src/storage/ClientChainGatewayStorage.sol index 86eee8ab..d32bbaca 100644 --- a/src/storage/ClientChainGatewayStorage.sol +++ b/src/storage/ClientChainGatewayStorage.sol @@ -28,7 +28,7 @@ contract ClientChainGatewayStorage is BootstrapStorage { uint256 internal constant GWEI_TO_WEI = 1e9; address internal constant VIRTUAL_STAKED_ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; IETHPOSDeposit internal constant ETH_POS = IETHPOSDeposit(0x00000000219ab540356cBB839Cbe05303d7705Fa); - uint256 internal constant TOKEN_ADDRESS_BYTES_LENTH = 32; + uint256 internal constant TOKEN_ADDRESS_BYTES_LENGTH = 32; uint256[40] private __gap; diff --git a/src/storage/ExocoreGatewayStorage.sol b/src/storage/ExocoreGatewayStorage.sol index af0b3506..06f2c777 100644 --- a/src/storage/ExocoreGatewayStorage.sol +++ b/src/storage/ExocoreGatewayStorage.sol @@ -16,7 +16,7 @@ contract ExocoreGatewayStorage is GatewayStorage { uint256 internal constant CLAIM_REWARD_REQUEST_LENGTH = 96; // bytes32 token + bytes32 delegator + bytes(42) operator + uint256 amount uint256 internal constant DEPOSIT_THEN_DELEGATE_REQUEST_LENGTH = DELEGATE_REQUEST_LENGTH; - uint256 internal constant TOKEN_ADDRESS_BYTES_LENTH = 32; + uint256 internal constant TOKEN_ADDRESS_BYTES_LENGTH = 32; uint256 internal constant UINT8_BYTES_LENGTH = 1; uint256 internal constant UINT256_BYTES_LENGTH = 32; diff --git a/test/foundry/unit/ExocoreGateway.t.sol b/test/foundry/unit/ExocoreGateway.t.sol index 69c63688..203b80e0 100644 --- a/test/foundry/unit/ExocoreGateway.t.sol +++ b/test/foundry/unit/ExocoreGateway.t.sol @@ -191,7 +191,7 @@ contract AddWhitelistTokens is SetUp { using stdStorage for StdStorage; using AddressCast for address; - uint256 internal constant TOKEN_ADDRESS_BYTES_LENTH = 32; + uint256 internal constant TOKEN_ADDRESS_BYTES_LENGTH = 32; event WhitelistTokenAdded(uint32 clientChainId, bytes32 token); @@ -204,7 +204,7 @@ contract AddWhitelistTokens is SetUp { function test_RevertWhen_CallerNotOwner() public { _prepareInputs(2); - uint256 messageLength = TOKEN_ADDRESS_BYTES_LENTH * whitelistTokens.length + 2; + uint256 messageLength = TOKEN_ADDRESS_BYTES_LENGTH * whitelistTokens.length + 2; uint256 nativeFee = exocoreGateway.quote(clientChainId, new bytes(messageLength)); vm.startPrank(deployer.addr); @@ -220,7 +220,7 @@ contract AddWhitelistTokens is SetUp { _prepareInputs(2); - uint256 messageLength = TOKEN_ADDRESS_BYTES_LENTH * whitelistTokens.length + 2; + uint256 messageLength = TOKEN_ADDRESS_BYTES_LENGTH * whitelistTokens.length + 2; uint256 nativeFee = exocoreGateway.quote(clientChainId, new bytes(messageLength)); vm.expectRevert(PausableUpgradeable.EnforcedPause.selector); exocoreGateway.addWhitelistTokens{value: nativeFee}( @@ -232,7 +232,7 @@ contract AddWhitelistTokens is SetUp { uint32 anotherClientChain = 3; _prepareInputs(2); - uint256 messageLength = TOKEN_ADDRESS_BYTES_LENTH * whitelistTokens.length + 2; + uint256 messageLength = TOKEN_ADDRESS_BYTES_LENGTH * whitelistTokens.length + 2; uint256 nativeFee = exocoreGateway.quote(clientChainId, new bytes(messageLength)); vm.startPrank(exocoreValidatorSet.addr); @@ -247,7 +247,7 @@ contract AddWhitelistTokens is SetUp { function test_RevertWhen_TokensListTooLong() public { _prepareInputs(256); - uint256 messageLength = TOKEN_ADDRESS_BYTES_LENTH * whitelistTokens.length + 2; + uint256 messageLength = TOKEN_ADDRESS_BYTES_LENGTH * whitelistTokens.length + 2; uint256 nativeFee = exocoreGateway.quote(clientChainId, new bytes(messageLength)); vm.startPrank(exocoreValidatorSet.addr); @@ -261,7 +261,7 @@ contract AddWhitelistTokens is SetUp { _prepareInputs(2); decimals.push(18); - uint256 messageLength = TOKEN_ADDRESS_BYTES_LENTH * whitelistTokens.length + 2; + uint256 messageLength = TOKEN_ADDRESS_BYTES_LENGTH * whitelistTokens.length + 2; uint256 nativeFee = exocoreGateway.quote(clientChainId, new bytes(messageLength)); vm.startPrank(exocoreValidatorSet.addr); @@ -277,7 +277,7 @@ contract AddWhitelistTokens is SetUp { tvlLimits[0] = 1e8 ether; tvlLimits[1] = 1e8 ether; - uint256 messageLength = TOKEN_ADDRESS_BYTES_LENTH * whitelistTokens.length + 2; + uint256 messageLength = TOKEN_ADDRESS_BYTES_LENGTH * whitelistTokens.length + 2; uint256 nativeFee = exocoreGateway.quote(clientChainId, new bytes(messageLength)); vm.startPrank(exocoreValidatorSet.addr); @@ -291,7 +291,7 @@ contract AddWhitelistTokens is SetUp { _prepareInputs(1); whitelistTokens[0] = bytes32(bytes20(address(restakeToken))); - uint256 messageLength = TOKEN_ADDRESS_BYTES_LENTH * whitelistTokens.length + 2; + uint256 messageLength = TOKEN_ADDRESS_BYTES_LENGTH * whitelistTokens.length + 2; uint256 nativeFee = exocoreGateway.quote(clientChainId, new bytes(messageLength)); vm.startPrank(exocoreValidatorSet.addr); @@ -309,7 +309,7 @@ contract AddWhitelistTokens is SetUp { names[0] = "RestakeToken"; metaData[0] = ""; - uint256 messageLength = TOKEN_ADDRESS_BYTES_LENTH * whitelistTokens.length + 2; + uint256 messageLength = TOKEN_ADDRESS_BYTES_LENGTH * whitelistTokens.length + 2; uint256 nativeFee = exocoreGateway.quote(clientChainId, new bytes(messageLength)); vm.startPrank(exocoreValidatorSet.addr); @@ -347,7 +347,7 @@ contract UpdateWhitelistTokens is SetUp { using AddressCast for address; - uint256 internal constant TOKEN_ADDRESS_BYTES_LENTH = 32; + uint256 internal constant TOKEN_ADDRESS_BYTES_LENGTH = 32; event WhitelistTokenUpdated(uint32 clientChainId, bytes32 token); @@ -489,7 +489,7 @@ contract UpdateWhitelistTokens is SetUp { string[] memory metaData_ ) internal { vm.startPrank(exocoreValidatorSet.addr); - uint256 messageLength = TOKEN_ADDRESS_BYTES_LENTH * whitelistTokens.length + 2; + uint256 messageLength = TOKEN_ADDRESS_BYTES_LENGTH * whitelistTokens.length + 2; uint256 nativeFee = exocoreGateway.quote(clientChainId, new bytes(messageLength)); exocoreGateway.addWhitelistTokens{value: nativeFee}( clientChainId_, whitelistTokens_, decimals_, tvlLimits_, names_, metaData_ From 2696f1cf81b579ff6ca25bd5572c2ccf606d830e Mon Sep 17 00:00:00 2001 From: adu Date: Mon, 1 Jul 2024 17:22:56 +0800 Subject: [PATCH 08/19] add nonReentrant modifier --- src/core/ExocoreGateway.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/ExocoreGateway.sol b/src/core/ExocoreGateway.sol index a672baca..519016ee 100644 --- a/src/core/ExocoreGateway.sol +++ b/src/core/ExocoreGateway.sol @@ -167,7 +167,7 @@ contract ExocoreGateway is uint256[] calldata tvlLimits, string[] calldata names, string[] calldata metaData - ) external payable onlyOwner whenNotPaused { + ) external payable onlyOwner whenNotPaused nonReentrant { _validateWhitelistTokensInput(clientChainId, tokens, decimals, tvlLimits, names, metaData); for (uint256 i; i < tokens.length; i++) { From 2927071e8c480bf1636aae507ec0496d8464cc97 Mon Sep 17 00:00:00 2001 From: adu Date: Tue, 2 Jul 2024 10:57:23 +0800 Subject: [PATCH 09/19] doc: add contract owner manual --- docs/contracts-owner-manual.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 docs/contracts-owner-manual.md diff --git a/docs/contracts-owner-manual.md b/docs/contracts-owner-manual.md new file mode 100644 index 00000000..a368549c --- /dev/null +++ b/docs/contracts-owner-manual.md @@ -0,0 +1,28 @@ +# Contract Owner's Manual + +Currently almost all contracts are ownable contracts including `Bootstrap`, `ClientChainGateway`, `Vault`, `ExocoreGateway`. The owner of these contracts is a privileged account in many aspects including upgrading contract by changing implementation, registering some client chain to Exocore before enabling restaking from that client chain, adding token info to whitelist and even pausing/unpausing contracts in case of emergencies. + +## Owner Only Functionalities + +## `pause`/`unpause` + +In case of emergencies where some unexpected errors happened to the protocol, contract owner could pause the contract to disable all user-facing functions and re-enable these functions after successfully recovering from emergencies. Currently pauseable contracts include `Bootstrap`, `ClientChainGateway`, `ExocoreGateway`. For `Vault` and `ExoCapsule`, as all the functions are limited to be called by their management contract(`ClientChainGateway`, `Bootstrap`), their functionalities are also paused when their management contract get paused. + +## upgrade to new implementation + +Most contracts including `Bootstrap`, `ClientChainGateway`, `ExocoreGateway`, `Vault` and `ExoCapsule` are upgradeable. For `Bootstrap`, `ClientChainGateway` and `ExocoreGateway`, they are upgradeable through `ProxyAdmin` contract, so the owner of `ProxyAdmin` could upgrade these contracts by changing implementation contract. +While for `Vault` and `ExoCapsule`, they are deployed with upgradeable beacon proxy, so only `upgradeableBeacon` could access the upgrading functionalities of these contracts, that is to say only the owner of `upgradeableBeacon` could upgrade these contracts by changing implementation contract. Notice as all `Vault`s are deployed with upgradeable beacon proxy and all these proxies point to the same `upgradeableBeacon` contract, when the owner change implementation contract stored in `upgradeableBeacon`, all `Vault`s get upgraded to the same new version of contract, and same case for `ExoCapsule`. + +## register client chain + +After all contracts are deployed, before the protocol starts to work, there are a few works left to be done by contract owner to enable restaking. One of the most important tasks is to registering the client chain id and meta info to Exocore to mark this client chain as valid. This is done by the contract caller calling `ExocoreGateway.registerOrUpdateClientChain` to write `clientChainId`, `addressLength`, `name`, `signatureType` and other meta data to Exocore native module to finish registration. This operation would also call `ExocoreGateway.setPeer` to enable LayerZero messaging by setting remote `ClientChainGateway` as trusted peer to send/receive messages. After finishing registration, contract owner could call `ExocoreGateway.registerOrUpdateClientChain` again to update the meta data and set new peer, or contract owner could solely call `ExocoreGateway.setPeer` to change the address of remote peer contract. + +## add tokens to whitelist + +Another important task before restaking being activated is to adding tokens to whitelist to mark them as stake-able on both Exocore and client chain. This is done by contract owner calling `ExocoreGateway.addWhitelistTokens` to write token addresses, decimals, TVL limits and other metadata to Exocore, as well as sending a cross-chain message through layerzero to client chain to add these token addresses to the whitelist of `ClientChainGateway`. + +Notice: contract owner must make sure the token data is correct like address, decimals and TVL limit, more importantly contract owner must ensure that for the same index, the data in different arrays like `tokens`, `decimals`, `tvlLimits` must point to the same token to be composed as complete token data. + +## upgrade the meta data of whitelisted tokens + +After adding tokens to whitelist, contract owner could call `ExocoreGateway.updateWhitelistedTokens` to update the meta data of already whitelisted tokens, and this function would not send a cross-chain message to client chain since the whitelist of `ClientChainGateway` only stores the token addresses. \ No newline at end of file From b98479cd1f6cf8ffc14b4324d70615a5b3cfbcbb Mon Sep 17 00:00:00 2001 From: adu Date: Tue, 2 Jul 2024 14:01:21 +0800 Subject: [PATCH 10/19] Update docs/contracts-owner-manual.md Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- docs/contracts-owner-manual.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/contracts-owner-manual.md b/docs/contracts-owner-manual.md index a368549c..60cb3e48 100644 --- a/docs/contracts-owner-manual.md +++ b/docs/contracts-owner-manual.md @@ -15,7 +15,7 @@ While for `Vault` and `ExoCapsule`, they are deployed with upgradeable beacon pr ## register client chain -After all contracts are deployed, before the protocol starts to work, there are a few works left to be done by contract owner to enable restaking. One of the most important tasks is to registering the client chain id and meta info to Exocore to mark this client chain as valid. This is done by the contract caller calling `ExocoreGateway.registerOrUpdateClientChain` to write `clientChainId`, `addressLength`, `name`, `signatureType` and other meta data to Exocore native module to finish registration. This operation would also call `ExocoreGateway.setPeer` to enable LayerZero messaging by setting remote `ClientChainGateway` as trusted peer to send/receive messages. After finishing registration, contract owner could call `ExocoreGateway.registerOrUpdateClientChain` again to update the meta data and set new peer, or contract owner could solely call `ExocoreGateway.setPeer` to change the address of remote peer contract. +After all contracts are deployed, before the protocol starts to work, there are a few works left to be done by contract owner to enable restaking. One of the most important tasks is to register the client chain id and meta info to Exocore to mark this client chain as valid. This is done by the contract caller calling `ExocoreGateway.registerOrUpdateClientChain` to write `clientChainId`, `addressLength`, `name`, `signatureType` and other meta data to Exocore native module to finish registration. This operation would also call `ExocoreGateway.setPeer` to enable LayerZero messaging by setting remote `ClientChainGateway` as trusted peer to send/receive messages. After finishing registration, contract owner could call `ExocoreGateway.registerOrUpdateClientChain` again to update the meta data and set new peer, or contract owner could solely call `ExocoreGateway.setPeer` to change the address of remote peer contract. ## add tokens to whitelist From f2f72f0816472686a67380b438b8e8ae3d9466cd Mon Sep 17 00:00:00 2001 From: adu Date: Tue, 2 Jul 2024 14:01:51 +0800 Subject: [PATCH 11/19] Update docs/contracts-owner-manual.md Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- docs/contracts-owner-manual.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/contracts-owner-manual.md b/docs/contracts-owner-manual.md index 60cb3e48..8d0cf1d2 100644 --- a/docs/contracts-owner-manual.md +++ b/docs/contracts-owner-manual.md @@ -19,7 +19,7 @@ After all contracts are deployed, before the protocol starts to work, there are ## add tokens to whitelist -Another important task before restaking being activated is to adding tokens to whitelist to mark them as stake-able on both Exocore and client chain. This is done by contract owner calling `ExocoreGateway.addWhitelistTokens` to write token addresses, decimals, TVL limits and other metadata to Exocore, as well as sending a cross-chain message through layerzero to client chain to add these token addresses to the whitelist of `ClientChainGateway`. +Another important task before restaking being activated is to add tokens to whitelist to mark them as stake-able on both Exocore and client chain. This is done by contract owner calling `ExocoreGateway.addWhitelistTokens` to write token addresses, decimals, TVL limits and other metadata to Exocore, as well as sending a cross-chain message through layerzero to client chain to add these token addresses to the whitelist of `ClientChainGateway`. Notice: contract owner must make sure the token data is correct like address, decimals and TVL limit, more importantly contract owner must ensure that for the same index, the data in different arrays like `tokens`, `decimals`, `tvlLimits` must point to the same token to be composed as complete token data. From f5a2a3afdc5999393959d48a93e19225d0714eb5 Mon Sep 17 00:00:00 2001 From: adu Date: Tue, 2 Jul 2024 14:02:27 +0800 Subject: [PATCH 12/19] Update docs/contracts-owner-manual.md Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- docs/contracts-owner-manual.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/contracts-owner-manual.md b/docs/contracts-owner-manual.md index 8d0cf1d2..4c6e08a0 100644 --- a/docs/contracts-owner-manual.md +++ b/docs/contracts-owner-manual.md @@ -6,7 +6,7 @@ Currently almost all contracts are ownable contracts including `Bootstrap`, `Cli ## `pause`/`unpause` -In case of emergencies where some unexpected errors happened to the protocol, contract owner could pause the contract to disable all user-facing functions and re-enable these functions after successfully recovering from emergencies. Currently pauseable contracts include `Bootstrap`, `ClientChainGateway`, `ExocoreGateway`. For `Vault` and `ExoCapsule`, as all the functions are limited to be called by their management contract(`ClientChainGateway`, `Bootstrap`), their functionalities are also paused when their management contract get paused. +In case of emergencies where some unexpected errors happened to the protocol, contract owner could pause the contract to disable all user-facing functions and re-enable these functions after successfully recovering from emergencies. Currently, pauseable contracts include `Bootstrap`, `ClientChainGateway`, `ExocoreGateway`. For `Vault` and `ExoCapsule`, as all the functions are limited to be called by their management contract(`ClientChainGateway`, `Bootstrap`), their functionalities are also paused when their management contract get paused. ## upgrade to new implementation From 5a1c08c7aa06dc458819b4bb417c795b116dbce5 Mon Sep 17 00:00:00 2001 From: adu Date: Tue, 2 Jul 2024 14:03:12 +0800 Subject: [PATCH 13/19] Update docs/contracts-owner-manual.md Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- docs/contracts-owner-manual.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/contracts-owner-manual.md b/docs/contracts-owner-manual.md index 4c6e08a0..e2a867a7 100644 --- a/docs/contracts-owner-manual.md +++ b/docs/contracts-owner-manual.md @@ -1,6 +1,6 @@ # Contract Owner's Manual -Currently almost all contracts are ownable contracts including `Bootstrap`, `ClientChainGateway`, `Vault`, `ExocoreGateway`. The owner of these contracts is a privileged account in many aspects including upgrading contract by changing implementation, registering some client chain to Exocore before enabling restaking from that client chain, adding token info to whitelist and even pausing/unpausing contracts in case of emergencies. +Currently, almost all contracts are ownable contracts including `Bootstrap`, `ClientChainGateway`, `Vault`, `ExocoreGateway`. The owner of these contracts is a privileged account in many aspects including upgrading contract by changing implementation, registering some client chain to Exocore before enabling restaking from that client chain, adding token info to whitelist and even pausing/unpausing contracts in case of emergencies. ## Owner Only Functionalities From 708cd22905102005d363ffb0ebceb7a4ef534f61 Mon Sep 17 00:00:00 2001 From: adu Date: Wed, 3 Jul 2024 17:48:02 +0800 Subject: [PATCH 14/19] test: remove unused event and fix script failure --- script/3_Setup.s.sol | 6 +++++- src/storage/ClientChainGatewayStorage.sol | 1 - test/foundry/ExocoreDeployer.t.sol | 1 - 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/script/3_Setup.s.sol b/script/3_Setup.s.sol index 2284f3c0..cdc419ff 100644 --- a/script/3_Setup.s.sol +++ b/script/3_Setup.s.sol @@ -40,6 +40,10 @@ contract SetupScript is BaseScript { exocoreLzEndpoint = ILayerZeroEndpointV2(stdJson.readAddress(deployedContracts, ".exocore.lzEndpoint")); require(address(exocoreLzEndpoint) != address(0), "exocoreLzEndpoint address should not be empty"); + if (!useExocorePrecompileMock) { + _bindPrecompileMocks(); + } + // transfer some gas fee to exocore validator set address clientChain = vm.createSelectFork(clientChainRPCURL); _topUpPlayer(clientChain, address(0), deployer, exocoreValidatorSet.addr, 0.2 ether); @@ -98,7 +102,7 @@ contract SetupScript is BaseScript { whitelistTokensBytes32[1] = bytes32(bytes20(VIRTUAL_STAKED_ETH_ADDRESS)); decimals[1] = 18; tvlLimits[1] = 1e8 ether; - names[1] = "RestakeToken"; + names[1] = "StakedETH"; metaData[1] = ""; uint256 messageLength = TOKEN_ADDRESS_BYTES_LENGTH * whitelistTokensBytes32.length + 2; diff --git a/src/storage/ClientChainGatewayStorage.sol b/src/storage/ClientChainGatewayStorage.sol index d32bbaca..5d3cfed6 100644 --- a/src/storage/ClientChainGatewayStorage.sol +++ b/src/storage/ClientChainGatewayStorage.sol @@ -43,7 +43,6 @@ contract ClientChainGatewayStorage is BootstrapStorage { /* ----------------------------- restaking ----------------------------- */ event ClaimSucceeded(address token, address recipient, uint256 amount); event WithdrawRewardResult(bool indexed success, address indexed token, address indexed withdrawer, uint256 amount); - event RegisterTokensResult(bool indexed success); /* -------------------------------------------------------------------------- */ /* Errors */ diff --git a/test/foundry/ExocoreDeployer.t.sol b/test/foundry/ExocoreDeployer.t.sol index 1b0e5bf4..ada6d332 100644 --- a/test/foundry/ExocoreDeployer.t.sol +++ b/test/foundry/ExocoreDeployer.t.sol @@ -89,7 +89,6 @@ contract ExocoreDeployer is Test { event MessageSent(GatewayStorage.Action indexed act, bytes32 packetId, uint64 nonce, uint256 nativeFee); event NewPacket(uint32, address, bytes32, uint64, bytes); - event RegisterTokensResult(bool indexed success); event WhitelistTokenAdded(address _token); event VaultCreated(address underlyingToken, address vault); From 2ca8cb588eaf4edd2abe848d2d085938112baa70 Mon Sep 17 00:00:00 2001 From: adu Date: Wed, 3 Jul 2024 20:23:04 +0800 Subject: [PATCH 15/19] test: fix script that registers client chain and tokens to whitelist --- script/3_Setup.s.sol | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/script/3_Setup.s.sol b/script/3_Setup.s.sol index cdc419ff..8deef48d 100644 --- a/script/3_Setup.s.sol +++ b/script/3_Setup.s.sol @@ -54,6 +54,7 @@ contract SetupScript is BaseScript { function run() public { // 1. setup client chain contracts to make them ready for sending and receiving messages from exocore gateway + vm.selectFork(clientChain); // Exocore validator set should be the owner of these contracts and only owner could setup contracts state vm.startBroadcast(exocoreValidatorSet.privateKey); @@ -69,8 +70,8 @@ contract SetupScript is BaseScript { vm.stopBroadcast(); // 2. setup Exocore contracts to make them ready for sending and receiving messages from client chain - // gateway, and register client chain meta data as well as well as adding tokens to whtielist to enable - // restaking + // gateway, and register client chain meta data to Exocore native module + vm.selectFork(exocore); // Exocore validator set should be the owner of these contracts and only owner could setup contracts state vm.startBroadcast(exocoreValidatorSet.privateKey); @@ -80,31 +81,41 @@ contract SetupScript is BaseScript { address(clientGateway), address(clientChainLzEndpoint) ); } - // first register clientChainId to Exocore native module and set peer for client chain gateway to be ready for + // register clientChainId to Exocore native module and set peer for client chain gateway to be ready for // messaging exocoreGateway.registerOrUpdateClientChain( - clientChainId, address(clientGateway).toBytes32(), 20, "clientChain", "", "secp256k1" + clientChainId, address(clientGateway).toBytes32(), 20, "ClientChain", "EVM compatible network", "secp256k1" ); - // second add whitelist tokens and their meta data on Exocore side to enable LST Restaking and Native Restaking, - // and this would also add token addresses to client chain gateway's whitelist + vm.stopBroadcast(); + + // 3. adding tokens to the whtielist of both Exocore and client chain gateway to enable restaking + + // first we read decimals from client chain ERC20 token contract to prepare for token data + vm.selectFork(clientChain); bytes32[] memory whitelistTokensBytes32 = new bytes32[](2); uint8[] memory decimals = new uint8[](2); uint256[] memory tvlLimits = new uint256[](2); string[] memory names = new string[](2); string[] memory metaData = new string[](2); + // this stands for LST restaking for restakeToken whitelistTokensBytes32[0] = bytes32(bytes20(address(restakeToken))); decimals[0] = restakeToken.decimals(); tvlLimits[0] = 1e10 ether; names[0] = "RestakeToken"; - metaData[0] = ""; + metaData[0] = "ERC20 LST token"; + // this stands for Native Restaking for ETH whitelistTokensBytes32[1] = bytes32(bytes20(VIRTUAL_STAKED_ETH_ADDRESS)); decimals[1] = 18; tvlLimits[1] = 1e8 ether; names[1] = "StakedETH"; - metaData[1] = ""; + metaData[1] = "natively staked ETH on Ethereum"; + vm.stopBroadcast(); + // second add whitelist tokens and their meta data on Exocore side to enable LST Restaking and Native Restaking, + // and this would also add token addresses to client chain gateway's whitelist + vm.selectFork(exocore); uint256 messageLength = TOKEN_ADDRESS_BYTES_LENGTH * whitelistTokensBytes32.length + 2; uint256 nativeFee = exocoreGateway.quote(clientChainId, new bytes(messageLength)); exocoreGateway.addWhitelistTokens{value: nativeFee}( From 1f65b36052f45415b3cb676a10f1e7fe9c9db755 Mon Sep 17 00:00:00 2001 From: adu Date: Wed, 3 Jul 2024 20:45:11 +0800 Subject: [PATCH 16/19] feat: add non-zero/non-empty check for function params --- src/core/ExocoreGateway.sol | 17 +++++++++++------ test/foundry/ExocoreDeployer.t.sol | 6 +++--- test/foundry/unit/ExocoreGateway.t.sol | 20 ++++++++++++++------ 3 files changed, 28 insertions(+), 15 deletions(-) diff --git a/src/core/ExocoreGateway.sol b/src/core/ExocoreGateway.sol index 519016ee..2f8d5ac7 100644 --- a/src/core/ExocoreGateway.sol +++ b/src/core/ExocoreGateway.sol @@ -134,7 +134,13 @@ contract ExocoreGateway is string calldata metaInfo, string calldata signatureType ) public onlyOwner whenNotPaused { - _validatePeer(clientChainId, clientChainGateway); + require(clientChainId != uint32(0), "ExocoreGateway: endpoint id cannot be zero"); + require(clientChainGateway != bytes32(0), "ExocoreGateway: client chain gateway cannot be empty"); + require(addressLength != 0, "ExocoreGateway: address length cannot be zero"); + require(bytes(name).length != 0, "ExocoreGateway: name cannot be empty"); + require(bytes(metaInfo).length != 0, "ExocoreGateway: meta data cannot be empty"); + // signature type could be left as empty for current implementation + _registerClientChain(clientChainId, addressLength, name, metaInfo, signatureType); super.setPeer(clientChainId, clientChainGateway); @@ -174,6 +180,8 @@ contract ExocoreGateway is require(tokens[i] != bytes32(0), "ExocoreGateway: token cannot be zero address"); require(!isWhitelistedToken[tokens[i]], "ExocoreGateway: token has already been added to whitelist before"); require(tvlLimits[i] > 0, "ExocoreGateway: tvl limit should not be zero"); + require(bytes(names[i]).length != 0, "ExocoreGateway: name cannot be empty"); + require(bytes(metaData[i]).length != 0, "ExocoreGateway: meta data cannot be empty"); bool success = ASSETS_CONTRACT.registerToken( clientChainId, abi.encodePacked(tokens[i]), decimals[i], tvlLimits[i], names[i], metaData[i] @@ -207,6 +215,8 @@ contract ExocoreGateway is require(tokens[i] != bytes32(0), "ExocoreGateway: token cannot be zero address"); require(isWhitelistedToken[tokens[i]], "ExocoreGateway: token has not been added to whitelist before"); require(tvlLimits[i] > 0, "ExocoreGateway: tvl limit should not be zero"); + require(bytes(names[i]).length != 0, "ExocoreGateway: name cannot be empty"); + require(bytes(metaData[i]).length != 0, "ExocoreGateway: meta data cannot be empty"); bool success = ASSETS_CONTRACT.registerToken( clientChainId, abi.encodePacked(tokens[i]), decimals[i], tvlLimits[i], names[i], metaData[i] @@ -245,11 +255,6 @@ contract ExocoreGateway is } } - function _validatePeer(uint32 clientChainId, bytes32 clientChainGateway) internal pure { - require(clientChainId != uint32(0), "ExocoreGateway: zero value is not invalid endpoint id"); - require(clientChainGateway != bytes32(0), "ExocoreGateway: client chain gateway cannot be empty"); - } - function _registerClientChain( uint32 clientChainId, uint8 addressLength, diff --git a/test/foundry/ExocoreDeployer.t.sol b/test/foundry/ExocoreDeployer.t.sol index ada6d332..99ad969f 100644 --- a/test/foundry/ExocoreDeployer.t.sol +++ b/test/foundry/ExocoreDeployer.t.sol @@ -130,13 +130,13 @@ contract ExocoreDeployer is Test { decimals[0] = 18; tvlLimits[0] = 1e8 ether; names[0] = "RestakeToken"; - metaData[0] = ""; + metaData[0] = "ERC20 LST token"; whitelistTokens.push(bytes32(bytes20(VIRTUAL_STAKED_ETH_ADDRESS))); decimals[1] = 18; tvlLimits[1] = 1e8 ether; names[1] = "NativeStakedETH"; - metaData[1] = ""; + metaData[1] = "natively staked ETH on Ethereum"; // -- add whitelist tokens workflow test -- @@ -312,7 +312,7 @@ contract ExocoreDeployer is Test { // messages. On Exocore side, this is done by calling registerClientChain clientGateway.setPeer(exocoreChainId, address(exocoreGateway).toBytes32()); exocoreGateway.registerOrUpdateClientChain( - clientChainId, address(clientGateway).toBytes32(), 20, "clientChain", "", "secp256k1" + clientChainId, address(clientGateway).toBytes32(), 20, "clientChain", "EVM compatible client chain", "secp256k1" ); vm.stopPrank(); } diff --git a/test/foundry/unit/ExocoreGateway.t.sol b/test/foundry/unit/ExocoreGateway.t.sol index 203b80e0..a4caf040 100644 --- a/test/foundry/unit/ExocoreGateway.t.sol +++ b/test/foundry/unit/ExocoreGateway.t.sol @@ -97,7 +97,7 @@ contract SetUp is Test { vm.startPrank(exocoreValidatorSet.addr); exocoreLzEndpoint.setDestLzEndpoint(address(clientGateway), address(clientLzEndpoint)); exocoreGateway.registerOrUpdateClientChain( - clientChainId, address(clientGateway).toBytes32(), 20, "clientChain", "", "secp256k1" + clientChainId, address(clientGateway).toBytes32(), 20, "clientChain", "EVM compatible client chain", "secp256k1" ); vm.stopPrank(); @@ -276,6 +276,10 @@ contract AddWhitelistTokens is SetUp { whitelistTokens[0] = bytes32(bytes20(address(restakeToken))); tvlLimits[0] = 1e8 ether; tvlLimits[1] = 1e8 ether; + names[0] = "LST-1"; + names[1] = "LST-2"; + metaData[0] = "LST token"; + metaData[1] = "LST token"; uint256 messageLength = TOKEN_ADDRESS_BYTES_LENGTH * whitelistTokens.length + 2; uint256 nativeFee = exocoreGateway.quote(clientChainId, new bytes(messageLength)); @@ -307,7 +311,7 @@ contract AddWhitelistTokens is SetUp { decimals[0] = 18; tvlLimits[0] = 1e8 ether; names[0] = "RestakeToken"; - metaData[0] = ""; + metaData[0] = "ERC20 LST token"; uint256 messageLength = TOKEN_ADDRESS_BYTES_LENGTH * whitelistTokens.length + 2; uint256 nativeFee = exocoreGateway.quote(clientChainId, new bytes(messageLength)); @@ -411,13 +415,17 @@ contract UpdateWhitelistTokens is SetUp { decimals[0] = 18; tvlLimits[0] = 1e8 ether; names[0] = "RestakeToken"; - metaData[0] = ""; + metaData[0] = "ERC20 LST token"; _addWhitelistTokens(clientChainId, whitelistTokens, decimals, tvlLimits, names, metaData); _prepareInputs(2); whitelistTokens[0] = bytes32(bytes20(address(restakeToken))); tvlLimits[0] = 1e8 ether; tvlLimits[1] = 1e8 ether; + names[0] = "LST-1"; + names[1] = "LST-2"; + metaData[0] = "LST token"; + metaData[1] = "LST token"; vm.startPrank(exocoreValidatorSet.addr); vm.expectRevert("ExocoreGateway: token cannot be zero address"); @@ -430,7 +438,7 @@ contract UpdateWhitelistTokens is SetUp { decimals[0] = 18; tvlLimits[0] = 1e8 ether; names[0] = "RestakeToken"; - metaData[0] = ""; + metaData[0] = "ERC20 LST token"; _addWhitelistTokens(clientChainId, whitelistTokens, decimals, tvlLimits, names, metaData); tvlLimits[0] = 0; @@ -446,7 +454,7 @@ contract UpdateWhitelistTokens is SetUp { decimals[0] = 18; tvlLimits[0] = 1e10 ether; names[0] = "RestakeToken"; - metaData[0] = ""; + metaData[0] = "ERC20 LST token"; vm.startPrank(exocoreValidatorSet.addr); vm.expectRevert("ExocoreGateway: token has not been added to whitelist before"); @@ -459,7 +467,7 @@ contract UpdateWhitelistTokens is SetUp { decimals[0] = 18; tvlLimits[0] = 1e8 ether; names[0] = "RestakeToken"; - metaData[0] = ""; + metaData[0] = "ERC20 LST token"; // add token to whitelist first _addWhitelistTokens(clientChainId, whitelistTokens, decimals, tvlLimits, names, metaData); From 3cd44bb19b08a64775ae67a6de74f80ab671e540 Mon Sep 17 00:00:00 2001 From: adu Date: Wed, 3 Jul 2024 20:46:33 +0800 Subject: [PATCH 17/19] chore: fmt and lint --- script/3_Setup.s.sol | 4 ++-- test/foundry/ExocoreDeployer.t.sol | 7 ++++++- test/foundry/unit/ExocoreGateway.t.sol | 7 ++++++- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/script/3_Setup.s.sol b/script/3_Setup.s.sol index 8deef48d..02fac89a 100644 --- a/script/3_Setup.s.sol +++ b/script/3_Setup.s.sol @@ -54,7 +54,7 @@ contract SetupScript is BaseScript { function run() public { // 1. setup client chain contracts to make them ready for sending and receiving messages from exocore gateway - + vm.selectFork(clientChain); // Exocore validator set should be the owner of these contracts and only owner could setup contracts state vm.startBroadcast(exocoreValidatorSet.privateKey); @@ -105,7 +105,7 @@ contract SetupScript is BaseScript { names[0] = "RestakeToken"; metaData[0] = "ERC20 LST token"; - // this stands for Native Restaking for ETH + // this stands for Native Restaking for ETH whitelistTokensBytes32[1] = bytes32(bytes20(VIRTUAL_STAKED_ETH_ADDRESS)); decimals[1] = 18; tvlLimits[1] = 1e8 ether; diff --git a/test/foundry/ExocoreDeployer.t.sol b/test/foundry/ExocoreDeployer.t.sol index 99ad969f..4672d654 100644 --- a/test/foundry/ExocoreDeployer.t.sol +++ b/test/foundry/ExocoreDeployer.t.sol @@ -312,7 +312,12 @@ contract ExocoreDeployer is Test { // messages. On Exocore side, this is done by calling registerClientChain clientGateway.setPeer(exocoreChainId, address(exocoreGateway).toBytes32()); exocoreGateway.registerOrUpdateClientChain( - clientChainId, address(clientGateway).toBytes32(), 20, "clientChain", "EVM compatible client chain", "secp256k1" + clientChainId, + address(clientGateway).toBytes32(), + 20, + "clientChain", + "EVM compatible client chain", + "secp256k1" ); vm.stopPrank(); } diff --git a/test/foundry/unit/ExocoreGateway.t.sol b/test/foundry/unit/ExocoreGateway.t.sol index a4caf040..9a4e6359 100644 --- a/test/foundry/unit/ExocoreGateway.t.sol +++ b/test/foundry/unit/ExocoreGateway.t.sol @@ -97,7 +97,12 @@ contract SetUp is Test { vm.startPrank(exocoreValidatorSet.addr); exocoreLzEndpoint.setDestLzEndpoint(address(clientGateway), address(clientLzEndpoint)); exocoreGateway.registerOrUpdateClientChain( - clientChainId, address(clientGateway).toBytes32(), 20, "clientChain", "EVM compatible client chain", "secp256k1" + clientChainId, + address(clientGateway).toBytes32(), + 20, + "clientChain", + "EVM compatible client chain", + "secp256k1" ); vm.stopPrank(); From 6c894d1be34532c14617241c3f4178514ced9144 Mon Sep 17 00:00:00 2001 From: adu Date: Thu, 4 Jul 2024 11:35:18 +0800 Subject: [PATCH 18/19] test: add unit tests for registerOrUpdateClientChain and setPeer --- src/core/ExocoreGateway.sol | 28 ++-- test/foundry/unit/ExocoreGateway.t.sol | 173 +++++++++++++++++++++++++ test/mocks/ExocoreGatewayMock.sol | 53 +++++--- 3 files changed, 220 insertions(+), 34 deletions(-) diff --git a/src/core/ExocoreGateway.sol b/src/core/ExocoreGateway.sol index 2f8d5ac7..e355d46d 100644 --- a/src/core/ExocoreGateway.sol +++ b/src/core/ExocoreGateway.sol @@ -113,10 +113,13 @@ contract ExocoreGateway is } /** - * @notice Sets the peer address (OApp instance) for a corresponding endpoint. This would also - * register the `cientChainId` to Exocore native module if the peer address is first time being set. + * @notice Register the `cientChainId` and othe meta data to Exocore native module or update clien chain's meta data + * according to the `clinetChainId`. + * And set trusted remote peer to enable layerzero messaging or other bridge messaging. * @param clientChainId The endpoint ID for client chain. - * @param clientChainGateway The contract address to be associated with the corresponding endpoint. + * @param peer The trusted remote contract address to be associated with the corresponding endpoint or some + * authorized signer that would be trusted for + * sending messages from/to source chain to/from this contract * @param addressLength The bytes length of address type on that client chain * @param name The name of client chain * @param metaInfo The arbitrary metadata for client chain @@ -128,21 +131,21 @@ contract ExocoreGateway is */ function registerOrUpdateClientChain( uint32 clientChainId, - bytes32 clientChainGateway, + bytes32 peer, uint8 addressLength, string calldata name, string calldata metaInfo, string calldata signatureType ) public onlyOwner whenNotPaused { - require(clientChainId != uint32(0), "ExocoreGateway: endpoint id cannot be zero"); - require(clientChainGateway != bytes32(0), "ExocoreGateway: client chain gateway cannot be empty"); - require(addressLength != 0, "ExocoreGateway: address length cannot be zero"); + require(clientChainId != uint32(0), "ExocoreGateway: client chain id cannot be zero or empty"); + require(peer != bytes32(0), "ExocoreGateway: peer address cannot be zero or empty"); + require(addressLength != 0, "ExocoreGateway: address length cannot be zero or empty"); require(bytes(name).length != 0, "ExocoreGateway: name cannot be empty"); require(bytes(metaInfo).length != 0, "ExocoreGateway: meta data cannot be empty"); // signature type could be left as empty for current implementation _registerClientChain(clientChainId, addressLength, name, metaInfo, signatureType); - super.setPeer(clientChainId, clientChainGateway); + super.setPeer(clientChainId, peer); if (!isRegisteredClientChain[clientChainId]) { isRegisteredClientChain[clientChainId] = true; @@ -262,12 +265,9 @@ contract ExocoreGateway is string calldata metaInfo, string calldata signatureType ) internal { - if (peers[clientChainId] == bytes32(0)) { - bool success = - ASSETS_CONTRACT.registerClientChain(clientChainId, addressLength, name, metaInfo, signatureType); - if (!success) { - revert RegisterClientChainToExocoreFailed(clientChainId); - } + bool success = ASSETS_CONTRACT.registerClientChain(clientChainId, addressLength, name, metaInfo, signatureType); + if (!success) { + revert RegisterClientChainToExocoreFailed(clientChainId); } } diff --git a/test/foundry/unit/ExocoreGateway.t.sol b/test/foundry/unit/ExocoreGateway.t.sol index 9a4e6359..699eea22 100644 --- a/test/foundry/unit/ExocoreGateway.t.sol +++ b/test/foundry/unit/ExocoreGateway.t.sol @@ -191,6 +191,179 @@ contract LzReceive is SetUp { } +contract RegisterOrUpdateClientChain is SetUp { + + using AddressCast for address; + + event ClientChainRegistered(uint32 clientChainId); + event ClientChainUpdated(uint32 clientChainId); + + uint32 anotherClientChain; + bytes32 peer; + uint8 addressLength; + string name; + string metaInfo; + string signatureType; + + function test_Success_RegisterClientChain() public { + _prepareClientChainData(); + + vm.expectEmit(true, true, true, true, address(exocoreGateway)); + emit ClientChainRegistered(anotherClientChain); + vm.startPrank(exocoreValidatorSet.addr); + exocoreGateway.registerOrUpdateClientChain( + anotherClientChain, peer, addressLength, name, metaInfo, signatureType + ); + } + + function test_Success_UpdateClientChain() public { + test_Success_RegisterClientChain(); + + peer = bytes32(uint256(321)); + metaInfo = "Testnet"; + + vm.expectEmit(true, true, true, true, address(exocoreGateway)); + emit ClientChainUpdated(anotherClientChain); + vm.startPrank(exocoreValidatorSet.addr); + exocoreGateway.registerOrUpdateClientChain( + anotherClientChain, peer, addressLength, name, metaInfo, signatureType + ); + } + + function test_RevertWhen_CallerNotOwner() public { + _prepareClientChainData(); + + vm.startPrank(deployer.addr); + vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, deployer.addr)); + exocoreGateway.registerOrUpdateClientChain( + anotherClientChain, peer, addressLength, name, metaInfo, signatureType + ); + } + + function test_RevertWhen_Paused() public { + vm.startPrank(exocoreValidatorSet.addr); + exocoreGateway.pause(); + + _prepareClientChainData(); + + vm.expectRevert(PausableUpgradeable.EnforcedPause.selector); + vm.startPrank(exocoreValidatorSet.addr); + exocoreGateway.registerOrUpdateClientChain( + anotherClientChain, peer, addressLength, name, metaInfo, signatureType + ); + } + + function test_RevertWhen_ZeroOrEmptyClientChain() public { + _prepareClientChainData(); + anotherClientChain = 0; + + vm.expectRevert("ExocoreGateway: client chain id cannot be zero or empty"); + vm.startPrank(exocoreValidatorSet.addr); + exocoreGateway.registerOrUpdateClientChain( + anotherClientChain, peer, addressLength, name, metaInfo, signatureType + ); + } + + function test_RevertWhen_ZeroOrEmptyPeer() public { + _prepareClientChainData(); + peer = bytes32(0); + + vm.expectRevert("ExocoreGateway: peer address cannot be zero or empty"); + vm.startPrank(exocoreValidatorSet.addr); + exocoreGateway.registerOrUpdateClientChain( + anotherClientChain, peer, addressLength, name, metaInfo, signatureType + ); + } + + function test_RevertWhen_ZeroOrEmptyAddressLength() public { + _prepareClientChainData(); + addressLength = 0; + + vm.expectRevert("ExocoreGateway: address length cannot be zero or empty"); + vm.startPrank(exocoreValidatorSet.addr); + exocoreGateway.registerOrUpdateClientChain( + anotherClientChain, peer, addressLength, name, metaInfo, signatureType + ); + } + + function test_RevertWhen_EmptyName() public { + _prepareClientChainData(); + name = ""; + + vm.expectRevert("ExocoreGateway: name cannot be empty"); + vm.startPrank(exocoreValidatorSet.addr); + exocoreGateway.registerOrUpdateClientChain( + anotherClientChain, peer, addressLength, name, metaInfo, signatureType + ); + } + + function test_RevertWhen_EmptyMetaInfo() public { + _prepareClientChainData(); + metaInfo = ""; + + vm.expectRevert("ExocoreGateway: meta data cannot be empty"); + vm.startPrank(exocoreValidatorSet.addr); + exocoreGateway.registerOrUpdateClientChain( + anotherClientChain, peer, addressLength, name, metaInfo, signatureType + ); + } + + function _prepareClientChainData() internal { + anotherClientChain = clientChainId + 1; + peer = bytes32(uint256(123)); + addressLength = 20; + name = "AnotherClientChain"; + metaInfo = "EVM compatible client chain"; + signatureType = "secp256k1"; + } + +} + +contract SetPeer is SetUp { + + ExocoreGateway gateway; + + uint32 anotherClientChain = clientChainId + 1; + bytes32 anotherPeer = bytes32("0xabcdef"); + bytes32 newPeer = bytes32("0x123"); + + event PeerSet(uint32 eid, bytes32 peer); + + function test_Success_SetPeer() public { + vm.startPrank(exocoreValidatorSet.addr); + vm.expectEmit(true, true, true, true, address(exocoreGateway)); + emit PeerSet(anotherClientChain, anotherPeer); + exocoreGateway.registerOrUpdateClientChain( + anotherClientChain, anotherPeer, 20, "Test Chain", "Test Meta", "ECDSA" + ); + + vm.expectEmit(true, true, true, true, address(exocoreGateway)); + emit PeerSet(anotherClientChain, newPeer); + exocoreGateway.setPeer(anotherClientChain, newPeer); + } + + function test_RevertWhen_CallerNotOwner() public { + vm.startPrank(exocoreValidatorSet.addr); + vm.expectEmit(true, true, true, true, address(exocoreGateway)); + emit PeerSet(anotherClientChain, anotherPeer); + exocoreGateway.registerOrUpdateClientChain( + anotherClientChain, anotherPeer, 20, "Test Chain", "Test Meta", "ECDSA" + ); + vm.stopPrank(); + + vm.startPrank(deployer.addr); + vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, deployer.addr)); + exocoreGateway.setPeer(anotherClientChain, newPeer); + } + + function test_RevertWhen_ClientChainNotRegistered() public { + vm.startPrank(exocoreValidatorSet.addr); + vm.expectRevert("ExocoreGateway: client chain should be registered before setting peer to change peer address"); + exocoreGateway.setPeer(anotherClientChain, newPeer); + } + +} + contract AddWhitelistTokens is SetUp { using stdStorage for StdStorage; diff --git a/test/mocks/ExocoreGatewayMock.sol b/test/mocks/ExocoreGatewayMock.sol index 187274a6..21d31069 100644 --- a/test/mocks/ExocoreGatewayMock.sol +++ b/test/mocks/ExocoreGatewayMock.sol @@ -21,12 +21,14 @@ import {ILayerZeroReceiver} from "@layerzero-v2/protocol/contracts/interfaces/IL import {OwnableUpgradeable} from "@openzeppelin-upgradeable/contracts/access/OwnableUpgradeable.sol"; import {Initializable} from "@openzeppelin-upgradeable/contracts/proxy/utils/Initializable.sol"; import {PausableUpgradeable} from "@openzeppelin-upgradeable/contracts/utils/PausableUpgradeable.sol"; +import {ReentrancyGuardUpgradeable} from "@openzeppelin-upgradeable/contracts/utils/ReentrancyGuardUpgradeable.sol"; import {OAppCoreUpgradeable} from "src/lzApp/OAppCoreUpgradeable.sol"; contract ExocoreGatewayMock is Initializable, PausableUpgradeable, OwnableUpgradeable, + ReentrancyGuardUpgradeable, IExocoreGateway, ExocoreGatewayStorage, OAppUpgradeable @@ -114,7 +116,7 @@ contract ExocoreGatewayMock is // setPeer) or be triggered by Golang after the contract is deployed. // For manual calls, this function should be called immediately after deployment and // then never needs to be called again. - function markBootstrapOnAllChains() public whenNotPaused { + function markBootstrapOnAllChains() public whenNotPaused nonReentrant { (bool success, bytes memory result) = ASSETS_PRECOMPILE_ADDRESS.staticcall(abi.encodeWithSelector(ASSETS_CONTRACT.getClientChains.selector)); require(success, "ExocoreGateway: failed to get client chain ids"); @@ -131,10 +133,13 @@ contract ExocoreGatewayMock is } /** - * @notice Sets the peer address (OApp instance) for a corresponding endpoint. This would also - * register the `cientChainId` to Exocore native module if the peer address is first time being set. + * @notice Register the `cientChainId` and othe meta data to Exocore native module or update clien chain's meta data + * according to the `clinetChainId`. + * And set trusted remote peer to enable layerzero messaging or other bridge messaging. * @param clientChainId The endpoint ID for client chain. - * @param clientChainGateway The contract address to be associated with the corresponding endpoint. + * @param peer The trusted remote contract address to be associated with the corresponding endpoint or some + * authorized signer that would be trusted for + * sending messages from/to source chain to/from this contract * @param addressLength The bytes length of address type on that client chain * @param name The name of client chain * @param metaInfo The arbitrary metadata for client chain @@ -146,15 +151,21 @@ contract ExocoreGatewayMock is */ function registerOrUpdateClientChain( uint32 clientChainId, - bytes32 clientChainGateway, + bytes32 peer, uint8 addressLength, string calldata name, string calldata metaInfo, string calldata signatureType ) public onlyOwner whenNotPaused { - _validatePeer(clientChainId, clientChainGateway); + require(clientChainId != uint32(0), "ExocoreGateway: client chain id cannot be zero or empty"); + require(peer != bytes32(0), "ExocoreGateway: peer address cannot be zero or empty"); + require(addressLength != 0, "ExocoreGateway: address length cannot be zero or empty"); + require(bytes(name).length != 0, "ExocoreGateway: name cannot be empty"); + require(bytes(metaInfo).length != 0, "ExocoreGateway: meta data cannot be empty"); + // signature type could be left as empty for current implementation + _registerClientChain(clientChainId, addressLength, name, metaInfo, signatureType); - super.setPeer(clientChainId, clientChainGateway); + super.setPeer(clientChainId, peer); if (!isRegisteredClientChain[clientChainId]) { isRegisteredClientChain[clientChainId] = true; @@ -185,13 +196,15 @@ contract ExocoreGatewayMock is uint256[] calldata tvlLimits, string[] calldata names, string[] calldata metaData - ) external payable onlyOwner whenNotPaused { + ) external payable onlyOwner whenNotPaused nonReentrant { _validateWhitelistTokensInput(clientChainId, tokens, decimals, tvlLimits, names, metaData); for (uint256 i; i < tokens.length; i++) { require(tokens[i] != bytes32(0), "ExocoreGateway: token cannot be zero address"); require(!isWhitelistedToken[tokens[i]], "ExocoreGateway: token has already been added to whitelist before"); require(tvlLimits[i] > 0, "ExocoreGateway: tvl limit should not be zero"); + require(bytes(names[i]).length != 0, "ExocoreGateway: name cannot be empty"); + require(bytes(metaData[i]).length != 0, "ExocoreGateway: meta data cannot be empty"); bool success = ASSETS_CONTRACT.registerToken( clientChainId, abi.encodePacked(tokens[i]), decimals[i], tvlLimits[i], names[i], metaData[i] @@ -225,6 +238,8 @@ contract ExocoreGatewayMock is require(tokens[i] != bytes32(0), "ExocoreGateway: token cannot be zero address"); require(isWhitelistedToken[tokens[i]], "ExocoreGateway: token has not been added to whitelist before"); require(tvlLimits[i] > 0, "ExocoreGateway: tvl limit should not be zero"); + require(bytes(names[i]).length != 0, "ExocoreGateway: name cannot be empty"); + require(bytes(metaData[i]).length != 0, "ExocoreGateway: meta data cannot be empty"); bool success = ASSETS_CONTRACT.registerToken( clientChainId, abi.encodePacked(tokens[i]), decimals[i], tvlLimits[i], names[i], metaData[i] @@ -263,11 +278,6 @@ contract ExocoreGatewayMock is } } - function _validatePeer(uint32 clientChainId, bytes32 clientChainGateway) internal pure { - require(clientChainId != uint32(0), "ExocoreGateway: zero value is not invalid endpoint id"); - require(clientChainGateway != bytes32(0), "ExocoreGateway: client chain gateway cannot be empty"); - } - function _registerClientChain( uint32 clientChainId, uint8 addressLength, @@ -275,16 +285,19 @@ contract ExocoreGatewayMock is string calldata metaInfo, string calldata signatureType ) internal { - if (peers[clientChainId] == bytes32(0)) { - bool success = - ASSETS_CONTRACT.registerClientChain(clientChainId, addressLength, name, metaInfo, signatureType); - if (!success) { - revert RegisterClientChainToExocoreFailed(clientChainId); - } + bool success = ASSETS_CONTRACT.registerClientChain(clientChainId, addressLength, name, metaInfo, signatureType); + if (!success) { + revert RegisterClientChainToExocoreFailed(clientChainId); } } - function _lzReceive(Origin calldata _origin, bytes calldata payload) internal virtual override whenNotPaused { + function _lzReceive(Origin calldata _origin, bytes calldata payload) + internal + virtual + override + whenNotPaused + nonReentrant + { _verifyAndUpdateNonce(_origin.srcEid, _origin.sender, _origin.nonce); Action act = Action(uint8(payload[0])); From 4dbea4f343a5d8c807b12a76bc11db064d3a1075 Mon Sep 17 00:00:00 2001 From: adu Date: Thu, 4 Jul 2024 15:25:48 +0800 Subject: [PATCH 19/19] chore: move shared constants to GatewayStorage --- src/storage/ClientChainGatewayStorage.sol | 3 --- src/storage/ExocoreGatewayStorage.sol | 4 ---- src/storage/GatewayStorage.sol | 5 +++++ 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/storage/ClientChainGatewayStorage.sol b/src/storage/ClientChainGatewayStorage.sol index 5d3cfed6..a457d836 100644 --- a/src/storage/ClientChainGatewayStorage.sol +++ b/src/storage/ClientChainGatewayStorage.sol @@ -23,12 +23,9 @@ contract ClientChainGatewayStorage is BootstrapStorage { IBeacon public immutable EXO_CAPSULE_BEACON; // constant state variables - uint128 internal constant DESTINATION_GAS_LIMIT = 500_000; - uint128 internal constant DESTINATION_MSG_VALUE = 0; uint256 internal constant GWEI_TO_WEI = 1e9; address internal constant VIRTUAL_STAKED_ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; IETHPOSDeposit internal constant ETH_POS = IETHPOSDeposit(0x00000000219ab540356cBB839Cbe05303d7705Fa); - uint256 internal constant TOKEN_ADDRESS_BYTES_LENGTH = 32; uint256[40] private __gap; diff --git a/src/storage/ExocoreGatewayStorage.sol b/src/storage/ExocoreGatewayStorage.sol index 06f2c777..88884e37 100644 --- a/src/storage/ExocoreGatewayStorage.sol +++ b/src/storage/ExocoreGatewayStorage.sol @@ -16,13 +16,9 @@ contract ExocoreGatewayStorage is GatewayStorage { uint256 internal constant CLAIM_REWARD_REQUEST_LENGTH = 96; // bytes32 token + bytes32 delegator + bytes(42) operator + uint256 amount uint256 internal constant DEPOSIT_THEN_DELEGATE_REQUEST_LENGTH = DELEGATE_REQUEST_LENGTH; - uint256 internal constant TOKEN_ADDRESS_BYTES_LENGTH = 32; uint256 internal constant UINT8_BYTES_LENGTH = 1; uint256 internal constant UINT256_BYTES_LENGTH = 32; - uint128 internal constant DESTINATION_GAS_LIMIT = 500_000; - uint128 internal constant DESTINATION_MSG_VALUE = 0; - mapping(uint32 clienChainId => bool) public chainToBootstrapped; mapping(uint32 clienChainId => bool registered) public isRegisteredClientChain; mapping(bytes32 token => bool whitelisted) public isWhitelistedToken; diff --git a/src/storage/GatewayStorage.sol b/src/storage/GatewayStorage.sol index b1b5d621..96bd9569 100644 --- a/src/storage/GatewayStorage.sol +++ b/src/storage/GatewayStorage.sol @@ -14,6 +14,11 @@ contract GatewayStorage { RESPOND } + /* ----------------- constants used for layerzero messaging ----------------- */ + uint256 internal constant TOKEN_ADDRESS_BYTES_LENGTH = 32; + uint128 internal constant DESTINATION_GAS_LIMIT = 500_000; + uint128 internal constant DESTINATION_MSG_VALUE = 0; + mapping(Action => bytes4) internal _whiteListFunctionSelectors; address payable public exocoreValidatorSetAddress;