From 37ec72882bad56184a257c12f9b5f4877c17a0cf Mon Sep 17 00:00:00 2001 From: MaxMustermann2 <82761650+MaxMustermann2@users.noreply.github.com> Date: Sat, 31 Aug 2024 15:24:08 +0000 Subject: [PATCH] feat(exo-gateway): add oracleInfo ExocoreNetwork/exocore#159 adds a new `oracleInfo` parameter to the `IAssets.sol` precompile when registering tokens. This PR follows that change. Previously, multiple tokens could be registered with a single call to ExocoreGateway. However, that resulted in too many variables (and too much gas) for Solidity to handle within a single function. To that end, and keeping in mind that addition of new tokens isn't likely to be a frequent occurrence, the function's capabilities have been tempered to support only a single token within the transaction. As a side effect, this fixes #80 --- script/3_Setup.s.sol | 40 ++- src/core/Bootstrap.sol | 8 +- src/core/ClientChainGateway.sol | 4 +- src/core/ClientGatewayLzReceiver.sol | 40 +-- src/core/ExocoreGateway.sol | 103 +++--- src/interfaces/IExocoreGateway.sol | 30 +- src/interfaces/precompiles/IAssets.sol | 4 +- src/libraries/Errors.sol | 7 +- src/storage/ExocoreGatewayStorage.sol | 8 +- src/storage/GatewayStorage.sol | 2 +- test/foundry/Delegation.t.sol | 4 +- test/foundry/DepositThenDelegateTo.t.sol | 4 +- test/foundry/DepositWithdrawPrinciple.t.sol | 8 +- test/foundry/ExocoreDeployer.t.sol | 118 ++++--- test/foundry/WithdrawReward.t.sol | 2 +- test/foundry/unit/ExocoreGateway.t.sol | 311 +++++++------------ test/mocks/AssetsMock.sol | 3 +- test/mocks/ExocoreGatewayMock.sol | 98 +++--- test/mocks/NonShortCircuitEndpointV2Mock.sol | 4 +- 19 files changed, 356 insertions(+), 442 deletions(-) diff --git a/script/3_Setup.s.sol b/script/3_Setup.s.sol index 966a35b4..234937e9 100644 --- a/script/3_Setup.s.sol +++ b/script/3_Setup.s.sol @@ -1,9 +1,11 @@ pragma solidity ^0.8.19; -import "../src/interfaces/IClientChainGateway.sol"; +import {GatewayStorage} from "../src/storage/GatewayStorage.sol"; +import "../src/interfaces/IClientChainGateway.sol"; import "../src/interfaces/IExocoreGateway.sol"; import "../src/interfaces/IVault.sol"; + import {NonShortCircuitEndpointV2Mock} from "../test/mocks/NonShortCircuitEndpointV2Mock.sol"; import {BaseScript} from "./BaseScript.sol"; @@ -86,41 +88,53 @@ contract SetupScript is BaseScript { exocoreGateway.registerOrUpdateClientChain( clientChainId, address(clientGateway).toBytes32(), 20, "ClientChain", "EVM compatible network", "secp256k1" ); - 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); + string[] memory metaDatas = new string[](2); + string[] memory oracleInfos = 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] = "ERC20 LST token"; + metaDatas[0] = "ERC20 LST token"; + oracleInfos[0] = "{'a': 'b'}"; // 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] = "natively staked ETH on Ethereum"; - vm.stopBroadcast(); + metaDatas[1] = "natively staked ETH on Ethereum"; + oracleInfos[1] = "{'b': 'a'}"; // 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.addOrUpdateWhitelistTokens{value: nativeFee}( - clientChainId, whitelistTokensBytes32, decimals, tvlLimits, names, metaData - ); + uint256 nativeFee; + for (uint256 i = 0; i < whitelistTokensBytes32.length; i++) { + nativeFee = exocoreGateway.quote( + clientChainId, + abi.encodePacked( + GatewayStorage.Action.REQUEST_ADD_WHITELIST_TOKEN, abi.encodePacked(whitelistTokensBytes32[i]) + ) + ); + exocoreGateway.addOrUpdateWhitelistToken{value: nativeFee}( + clientChainId, + whitelistTokensBytes32[i], + decimals[i], + tvlLimits[i], + names[i], + metaDatas[i], + oracleInfos[i] + ); + } vm.stopBroadcast(); } diff --git a/src/core/Bootstrap.sol b/src/core/Bootstrap.sol index 3afce2ad..8d1a1164 100644 --- a/src/core/Bootstrap.sol +++ b/src/core/Bootstrap.sol @@ -195,10 +195,10 @@ contract Bootstrap is whitelistTokens.push(token); isWhitelistedToken[token] = true; - // deploy the corresponding vault if not deployed before - if (address(tokenToVault[token]) == address(0)) { - _deployVault(token); - } + // tokens cannot be removed from the whitelist. hence, if the token is not in the + // whitelist, it means that it is missing a vault. we do not need to check for a + // pre-existing vault. + _deployVault(token); emit WhitelistTokenAdded(token); } diff --git a/src/core/ClientChainGateway.sol b/src/core/ClientChainGateway.sol index c652bded..8144df1a 100644 --- a/src/core/ClientChainGateway.sol +++ b/src/core/ClientChainGateway.sol @@ -69,8 +69,8 @@ contract ClientChainGateway is revert Errors.ZeroAddress(); } - _whiteListFunctionSelectors[Action.REQUEST_ADD_WHITELIST_TOKENS] = - this.afterReceiveAddWhitelistTokensRequest.selector; + _whiteListFunctionSelectors[Action.REQUEST_ADD_WHITELIST_TOKEN] = + this.afterReceiveAddWhitelistTokenRequest.selector; bootstrapped = true; diff --git a/src/core/ClientGatewayLzReceiver.sol b/src/core/ClientGatewayLzReceiver.sol index 8fa361ed..3d7af67b 100644 --- a/src/core/ClientGatewayLzReceiver.sol +++ b/src/core/ClientGatewayLzReceiver.sol @@ -290,40 +290,34 @@ abstract contract ClientGatewayLzReceiver is PausableUpgradeable, OAppReceiverUp } } - /// @notice Called after an add-whitelist-tokens response is received. + /// @notice Called after an add-whitelist-token response is received. /// @param requestPayload The request payload. // Though `_deployVault` would make external call to newly created `Vault` contract and initialize it, // `Vault` contract belongs to Exocore and we could make sure its implementation does not have dangerous behavior // like reentrancy. // slither-disable-next-line reentrancy-no-eth - function afterReceiveAddWhitelistTokensRequest(bytes calldata requestPayload) + function afterReceiveAddWhitelistTokenRequest(bytes calldata requestPayload) public onlyCalledFromThis whenNotPaused { - uint8 count = uint8(requestPayload[0]); - uint256 expectedLength = count * TOKEN_ADDRESS_BYTES_LENGTH + 1; - if (requestPayload.length != expectedLength) { - revert InvalidAddWhitelistTokensRequest(expectedLength, requestPayload.length); + address token = address(bytes20(abi.decode(requestPayload, (bytes32)))); + if (token == address(0)) { + revert Errors.ZeroAddress(); } - - for (uint256 i = 0; i < count; ++i) { - 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]) { - 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); - } + if (isWhitelistedToken[token]) { + // grave error, should never happen + revert Errors.ClientChainGatewayAlreadyWhitelisted(token); + } + isWhitelistedToken[token] = true; + whitelistTokens.push(token); + // deploy the corresponding vault if not deployed before + if (token != VIRTUAL_STAKED_ETH_ADDRESS) { + // since it is no longer possible to remove a token from the whitelist, the check for existing vault + // is not necessary. + _deployVault(token); } + emit WhitelistTokenAdded(token); } } diff --git a/src/core/ExocoreGateway.sol b/src/core/ExocoreGateway.sol index 5a0ea9f3..3cf5f9b4 100644 --- a/src/core/ExocoreGateway.sol +++ b/src/core/ExocoreGateway.sol @@ -177,51 +177,52 @@ contract ExocoreGateway is /// @notice If we want to activate client chain's native restaking, we should add the corresponding virtual /// token address to the whitelist, bytes32(bytes20(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE)) for Ethereum /// native restaking for example. - function addOrUpdateWhitelistTokens( + function addOrUpdateWhitelistToken( uint32 clientChainId, - bytes32[] calldata tokens, - uint8[] calldata decimals, - uint256[] calldata tvlLimits, - string[] calldata names, - string[] calldata metaData + bytes32 token, + uint8 decimals, + uint256 tvlLimit, + string calldata name, + string calldata metaData, + string calldata oracleInfo ) external payable onlyOwner whenNotPaused nonReentrant { - // The registration of the client chain is left for the precompile to validate. - _validateWhitelistTokensInput(tokens, decimals, tvlLimits, names, metaData); - - bool success; - bool updated; - for (uint256 i = 0; i < tokens.length; ++i) { - require(tokens[i] != bytes32(0), "ExocoreGateway: token cannot be zero address"); - 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"); - - (success, updated) = ASSETS_CONTRACT.registerOrUpdateTokens( - clientChainId, abi.encodePacked(tokens[i]), decimals[i], tvlLimits[i], names[i], metaData[i] - ); + require(token != bytes32(0), "ExocoreGateway: token cannot be zero address"); + require(tvlLimit > 0, "ExocoreGateway: tvl limit should not be zero"); + require(bytes(name).length != 0, "ExocoreGateway: name cannot be empty"); + require(bytes(metaData).length != 0, "ExocoreGateway: meta data cannot be empty"); + require(bytes(oracleInfo).length != 0, "ExocoreGateway: oracleInfo cannot be empty"); + + (bool success, bool updated) = ASSETS_CONTRACT.registerOrUpdateTokens( + clientChainId, + abi.encodePacked(token), // convert to bytes from bytes32 + decimals, + tvlLimit, + name, + metaData, + oracleInfo + ); - if (success) { - if (!updated) { - emit WhitelistTokenAdded(clientChainId, tokens[i]); - } else { - emit WhitelistTokenUpdated(clientChainId, tokens[i]); + if (success) { + if (!updated) { + if (msg.value == 0) { + revert Errors.ZeroValue(); } + emit WhitelistTokenAdded(clientChainId, token); + _sendInterchainMsg( + clientChainId, + Action.REQUEST_ADD_WHITELIST_TOKEN, + abi.encodePacked(token), // convert for decoding it on the receiving end + false + ); } else { - if (!updated) { - revert AddWhitelistTokenFailed(tokens[i]); - } else { - revert UpdateWhitelistTokenFailed(tokens[i]); + if (msg.value != 0) { + revert Errors.NonZeroValue(); } + emit WhitelistTokenUpdated(clientChainId, token); } - } - - if (!updated) { - _sendInterchainMsg( - clientChainId, - Action.REQUEST_ADD_WHITELIST_TOKENS, - abi.encodePacked(uint8(tokens.length), tokens), - false - ); + } else { + // if the precompile call didn't succeed, we don't know if it is an update. + revert AddOrUpdateWhitelistTokenFailed(token); } } @@ -258,32 +259,6 @@ contract ExocoreGateway is } } - /// @dev Validates the input for whitelist tokens. - /// @param tokens The list of token addresses, length must be <= 255. - /// @param decimals The list of token decimals, length must be equal to that of @param tokens. - /// @param tvlLimits The list of token TVL limits, length must be equal to that of @param tokens. - /// @param names The list of token names, length must be equal to that of @param tokens. - /// @param metaData The list of token meta data, length must be equal to that of @param tokens. - function _validateWhitelistTokensInput( - bytes32[] calldata tokens, - uint8[] calldata decimals, - uint256[] calldata tvlLimits, - string[] calldata names, - string[] calldata metaData - ) internal pure { - 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(); - } - } - /// @dev Validates that the client chain id is registered. /// @dev This is designed to be called only in the cases wherein the precompile isn't used. /// @dev In all other situations, it is the responsibility of the precompile to perform such diff --git a/src/interfaces/IExocoreGateway.sol b/src/interfaces/IExocoreGateway.sol index c43e2dbe..661db622 100644 --- a/src/interfaces/IExocoreGateway.sol +++ b/src/interfaces/IExocoreGateway.sol @@ -41,21 +41,27 @@ interface IExocoreGateway is IOAppReceiver, IOAppCore { string calldata signatureType ) external; - /// @notice Adds a list of whitelisted tokens to the client chain. + /// @notice Add a single whitelisted token to the client chain. /// @param clientChainId The LayerZero chain id of the client chain. - /// @param tokens The list of token addresses to be whitelisted. - /// @param decimals The list of token decimals, in the same order as the tokens list. - /// @param tvlLimits The list of token TVL limits (typically max supply),in the same order as the tokens list. - /// @param names The names of the tokens, in the same order as the tokens list. - /// @param metaData The meta information of the tokens, in the same order as the tokens list. + /// @param token The token address to be whitelisted. + /// @param decimals The decimals of the token. + /// @param tvlLimit The TVL limit of the token. + /// @param name The name of the token. + /// @param metaData The meta information of the token. + /// @param oracleInfo The oracle information of the token. /// @dev The chain must be registered before adding tokens. - function addOrUpdateWhitelistTokens( + /// @dev This function is payable because it sends a message to the client chain if + /// the token is not already registered. + /// @dev Previously, we tried to use this function for multiple tokens, but that + /// results in too many local variables (stack too deep). + function addOrUpdateWhitelistToken( uint32 clientChainId, - bytes32[] calldata tokens, - uint8[] calldata decimals, - uint256[] calldata tvlLimits, - string[] calldata names, - string[] calldata metaData + bytes32 token, + uint8 decimals, + uint256 tvlLimit, + string calldata name, + string calldata metaData, + string calldata oracleInfo ) external payable; } diff --git a/src/interfaces/precompiles/IAssets.sol b/src/interfaces/precompiles/IAssets.sol index 65580341..2d8fa035 100644 --- a/src/interfaces/precompiles/IAssets.sol +++ b/src/interfaces/precompiles/IAssets.sol @@ -65,6 +65,7 @@ interface IAssets { /// maxSupply if there is no limit /// @param name is the name of the token /// @param metaData is the arbitrary metadata of the token + /// @param oracleInfo is the oracle information of the token /// @return success if the token registration is successful /// @return updated whether the token was added or updated function registerOrUpdateTokens( @@ -73,7 +74,8 @@ interface IAssets { uint8 decimals, uint256 tvlLimit, string calldata name, - string calldata metaData + string calldata metaData, + string calldata oracleInfo ) external returns (bool success, bool updated); /// QUERIES diff --git a/src/libraries/Errors.sol b/src/libraries/Errors.sol index c6ad9afb..9e78b6bf 100644 --- a/src/libraries/Errors.sol +++ b/src/libraries/Errors.sol @@ -23,11 +23,10 @@ library Errors { /// @dev Thrown when passed-in amount is zero error ZeroAmount(); - /// @dev Thrown when the passed-in value is not zero + /// @dev Thrown when the passed-in msg.value is not zero but should be error NonZeroValue(); - /// @dev Thrown wehn the passed-in value is zero - /// @dev This is used when the value in question is not an amount + /// @dev Thrown when the passed-in msg.value is zero but should not be error ZeroValue(); /// @dev Index out of array bounds @@ -124,7 +123,7 @@ library Errors { error ClientChainGatewayAddWhitelistTooManyTokens(); /// @dev ClientChainGateway: token should not be whitelisted before - error ClientChainGatewayAlreadyWhitelisted(); + error ClientChainGatewayAlreadyWhitelisted(address token); ////////////////////////////////////// // ClientGatewayLzReceiver Errors // diff --git a/src/storage/ExocoreGatewayStorage.sol b/src/storage/ExocoreGatewayStorage.sol index 04560ffc..03e5c958 100644 --- a/src/storage/ExocoreGatewayStorage.sol +++ b/src/storage/ExocoreGatewayStorage.sol @@ -156,13 +156,9 @@ contract ExocoreGatewayStorage is GatewayStorage { /// @param clientChainId The LayerZero chain ID of the client chain. error RegisterClientChainToExocoreFailed(uint32 clientChainId); - /// @notice Thrown when a whitelist token addition fails + /// @notice Thrown when a whitelist token addition or update fails /// @param token The address of the token. - error AddWhitelistTokenFailed(bytes32 token); - - /// @notice Thrown when a whitelist token update fails - /// @param token The address of the token. - error UpdateWhitelistTokenFailed(bytes32 token); + error AddOrUpdateWhitelistTokenFailed(bytes32 token); /// @notice Thrown when the whitelist tokens input is invalid. error InvalidWhitelistTokensInput(); diff --git a/src/storage/GatewayStorage.sol b/src/storage/GatewayStorage.sol index b46e9cb7..d117acf4 100644 --- a/src/storage/GatewayStorage.sol +++ b/src/storage/GatewayStorage.sol @@ -15,7 +15,7 @@ contract GatewayStorage { REQUEST_UNDELEGATE_FROM, REQUEST_DEPOSIT_THEN_DELEGATE_TO, REQUEST_MARK_BOOTSTRAP, - REQUEST_ADD_WHITELIST_TOKENS, + REQUEST_ADD_WHITELIST_TOKEN, REQUEST_ASSOCIATE_OPERATOR, REQUEST_DISSOCIATE_OPERATOR, RESPOND diff --git a/test/foundry/Delegation.t.sol b/test/foundry/Delegation.t.sol index f1078e1d..ee1f92c3 100644 --- a/test/foundry/Delegation.t.sol +++ b/test/foundry/Delegation.t.sol @@ -120,7 +120,7 @@ contract DelegateTest is ExocoreDeployer { // 2. second layerzero relayers should watch the request message packet and relay the message to destination // endpoint - uint64 delegateResponseNonce = 2; + uint64 delegateResponseNonce = 3; bytes memory delegateResponsePayload = abi.encodePacked(GatewayStorage.Action.RESPOND, delegateRequestNonce, true); uint256 responseNativeFee = exocoreGateway.quote(clientChainId, delegateResponsePayload); @@ -243,7 +243,7 @@ contract DelegateTest is ExocoreDeployer { // 2. second layerzero relayers should watch the request message packet and relay the message to destination // endpoint - uint64 undelegateResponseNonce = 3; + uint64 undelegateResponseNonce = 4; bytes memory undelegateResponsePayload = abi.encodePacked(GatewayStorage.Action.RESPOND, undelegateRequestNonce, true); uint256 responseNativeFee = exocoreGateway.quote(clientChainId, undelegateResponsePayload); diff --git a/test/foundry/DepositThenDelegateTo.t.sol b/test/foundry/DepositThenDelegateTo.t.sol index a30494d4..e4d25e6f 100644 --- a/test/foundry/DepositThenDelegateTo.t.sol +++ b/test/foundry/DepositThenDelegateTo.t.sol @@ -46,7 +46,7 @@ contract DepositThenDelegateToTest is ExocoreDeployer { deal(address(exocoreGateway), 1e22); uint64 requestLzNonce = 1; - uint64 responseLzNonce = 2; + uint64 responseLzNonce = 3; // 2 tokens are whitelisted, 3 is response uint256 delegateAmount = 10_000; // before all operations we should add whitelist tokens @@ -85,7 +85,7 @@ contract DepositThenDelegateToTest is ExocoreDeployer { deal(address(exocoreGateway), 1e22); uint64 requestLzNonce = 1; - uint64 responseLzNonce = 2; + uint64 responseLzNonce = 3; uint256 delegateAmount = 10_000; // before all operations we should add whitelist tokens diff --git a/test/foundry/DepositWithdrawPrinciple.t.sol b/test/foundry/DepositWithdrawPrinciple.t.sol index dea6ad03..24bc39f7 100644 --- a/test/foundry/DepositWithdrawPrinciple.t.sol +++ b/test/foundry/DepositWithdrawPrinciple.t.sol @@ -111,7 +111,7 @@ contract DepositWithdrawPrincipalTest is ExocoreDeployer { // exocore gateway should return response message to exocore network layerzero endpoint vm.expectEmit(true, true, true, true, address(exocoreLzEndpoint)); lastlyUpdatedPrincipalBalance += depositAmount; - uint64 depositResponseNonce = 2; + uint64 depositResponseNonce = 3; bytes memory depositResponsePayload = abi.encodePacked(GatewayStorage.Action.RESPOND, depositRequestNonce, true, lastlyUpdatedPrincipalBalance); uint256 depositResponseNativeFee = exocoreGateway.quote(clientChainId, depositResponsePayload); @@ -196,7 +196,7 @@ contract DepositWithdrawPrincipalTest is ExocoreDeployer { // second layerzero relayers should watch the request message packet and relay the message to destination // endpoint - uint64 withdrawResponseNonce = 3; + uint64 withdrawResponseNonce = 4; lastlyUpdatedPrincipalBalance -= withdrawAmount; bytes memory withdrawResponsePayload = abi.encodePacked(GatewayStorage.Action.RESPOND, withdrawRequestNonce, true, lastlyUpdatedPrincipalBalance); @@ -344,7 +344,7 @@ contract DepositWithdrawPrincipalTest is ExocoreDeployer { // endpoint /// exocore gateway should return response message to exocore network layerzero endpoint - uint64 depositResponseNonce = 2; + uint64 depositResponseNonce = 3; lastlyUpdatedPrincipalBalance += depositAmount; bytes memory depositResponsePayload = abi.encodePacked(GatewayStorage.Action.RESPOND, depositRequestNonce, true, lastlyUpdatedPrincipalBalance); @@ -512,7 +512,7 @@ contract DepositWithdrawPrincipalTest is ExocoreDeployer { vm.stopPrank(); /// exocore gateway should return response message to exocore network layerzero endpoint - uint64 withdrawResponseNonce = 3; + uint64 withdrawResponseNonce = 4; lastlyUpdatedPrincipalBalance -= withdrawalAmount; bytes memory withdrawResponsePayload = abi.encodePacked(GatewayStorage.Action.RESPOND, withdrawRequestNonce, true, lastlyUpdatedPrincipalBalance); diff --git a/test/foundry/ExocoreDeployer.t.sol b/test/foundry/ExocoreDeployer.t.sol index 1b8bcb68..d49e7693 100644 --- a/test/foundry/ExocoreDeployer.t.sol +++ b/test/foundry/ExocoreDeployer.t.sol @@ -136,56 +136,87 @@ contract ExocoreDeployer is Test { uint8[] memory decimals = new uint8[](2); uint256[] memory tvlLimits = new uint256[](2); string[] memory names = new string[](2); - string[] memory metaData = new string[](2); + string[] memory metaDatas = new string[](2); + string[] memory oracleInfos = new string[](2); + bytes[] memory payloads = new bytes[](2); + bytes32[] memory requestIds = new bytes32[](2); whitelistTokens.push(bytes32(bytes20(address(restakeToken)))); decimals[0] = 18; tvlLimits[0] = 1e8 ether; names[0] = "RestakeToken"; - metaData[0] = "ERC20 LST token"; + metaDatas[0] = "ERC20 LST token"; + oracleInfos[0] = "{'a': 'b'}"; whitelistTokens.push(bytes32(bytes20(VIRTUAL_STAKED_ETH_ADDRESS))); decimals[1] = 18; tvlLimits[1] = 1e8 ether; names[1] = "NativeStakedETH"; - metaData[1] = "natively staked ETH on Ethereum"; + metaDatas[1] = "natively staked ETH on Ethereum"; + oracleInfos[1] = "{'b': 'a'}"; // -- add whitelist tokens workflow test -- vm.startPrank(exocoreValidatorSet.addr); + uint256 nativeFee; + for (uint256 i = 0; i < whitelistTokens.length; i++) { + payloads[i] = abi.encodePacked( + GatewayStorage.Action.REQUEST_ADD_WHITELIST_TOKEN, abi.encodePacked(whitelistTokens[i]) + ); + nativeFee = exocoreGateway.quote(clientChainId, payloads[i]); + requestIds[i] = generateUID(uint64(i + 1), false); + vm.expectEmit(address(exocoreLzEndpoint)); + emit NewPacket( + clientChainId, + address(exocoreGateway), + address(clientGateway).toBytes32(), + uint64(i) + 1, // nonce + payloads[i] + ); + vm.expectEmit(address(exocoreGateway)); + emit MessageSent( + GatewayStorage.Action.REQUEST_ADD_WHITELIST_TOKEN, + requestIds[i], + uint64(i) + 1, // nonce + nativeFee + ); + exocoreGateway.addOrUpdateWhitelistToken{value: nativeFee}( + clientChainId, whitelistTokens[i], decimals[i], tvlLimits[i], names[i], metaDatas[i], oracleInfos[i] + ); + } - // 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_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, false); - - // exocore layerzero endpoint should emit the message packet including whitelist tokens payload. - vm.expectEmit(true, true, true, true, address(exocoreLzEndpoint)); - emit NewPacket( - clientChainId, - address(exocoreGateway), - address(clientGateway).toBytes32(), - uint64(1), - registerTokensRequestPayload - ); - // exocore gateway gateway should emit MessageSent event - vm.expectEmit(true, true, true, true, address(exocoreGateway)); - emit MessageSent( - GatewayStorage.Action.REQUEST_ADD_WHITELIST_TOKENS, - registerTokensRequestId, - uint64(1), - registerTokensRequestNativeFee - ); - exocoreGateway.addOrUpdateWhitelistTokens{value: registerTokensRequestNativeFee}( - clientChainId, whitelistTokens, decimals, tvlLimits, names, metaData - ); + // // 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_ADD_WHITELIST_TOKEN, + // uint8(whitelistTokens.length), + // bytes32(bytes20(address(restakeToken))), + // bytes32(bytes20(VIRTUAL_STAKED_ETH_ADDRESS)) + // ); + // uint256 registerTokensRequestNativeFee = clientGateway.quote(registerTokensRequestPayload); + // bytes32 registerTokensRequestId = generateUID(1, false); + + // // exocore layerzero endpoint should emit the message packet including whitelist tokens payload. + // vm.expectEmit(true, true, true, true, address(exocoreLzEndpoint)); + // emit NewPacket( + // clientChainId, + // address(exocoreGateway), + // address(clientGateway).toBytes32(), + // uint64(1), + // registerTokensRequestPayload + // ); + // // exocore gateway gateway should emit MessageSent event + // vm.expectEmit(true, true, true, true, address(exocoreGateway)); + // emit MessageSent( + // GatewayStorage.Action.REQUEST_ADD_WHITELIST_TOKENS, + // registerTokensRequestId, + // uint64(1), + // registerTokensRequestNativeFee + // ); + // exocoreGateway.addOrUpdateWhitelistTokens{value: registerTokensRequestNativeFee}( + // clientChainId, whitelistTokens, decimals, tvlLimits, names, metaData + // ); // second layerzero relayers should watch the request message packet and relay the message to destination // endpoint @@ -195,15 +226,24 @@ contract ExocoreDeployer is Test { keccak256(abi.encodePacked(BEACON_PROXY_BYTECODE, abi.encode(address(vaultBeacon), ""))), address(clientGateway) ); - vm.expectEmit(true, true, true, true, address(clientGateway)); + vm.expectEmit(address(clientGateway)); emit VaultCreated(address(restakeToken), expectedVault); emit WhitelistTokenAdded(address(restakeToken)); - emit WhitelistTokenAdded(VIRTUAL_STAKED_ETH_ADDRESS); clientChainLzEndpoint.lzReceive( Origin(exocoreChainId, address(exocoreGateway).toBytes32(), uint64(1)), address(clientGateway), - registerTokensRequestId, - registerTokensRequestPayload, + requestIds[0], + payloads[0], + bytes("") + ); + + vm.expectEmit(address(clientGateway)); + emit WhitelistTokenAdded(VIRTUAL_STAKED_ETH_ADDRESS); + clientChainLzEndpoint.lzReceive( + Origin(exocoreChainId, address(exocoreGateway).toBytes32(), uint64(2)), + address(clientGateway), + requestIds[1], + payloads[1], bytes("") ); diff --git a/test/foundry/WithdrawReward.t.sol b/test/foundry/WithdrawReward.t.sol index 43446489..6336111b 100644 --- a/test/foundry/WithdrawReward.t.sol +++ b/test/foundry/WithdrawReward.t.sol @@ -71,7 +71,7 @@ contract WithdrawRewardTest is ExocoreDeployer { // endpoint // exocore gateway should return response message to exocore network layerzero endpoint - uint64 withdrawResponseNonce = 2; + uint64 withdrawResponseNonce = 3; bytes memory withdrawResponsePayload = abi.encodePacked(GatewayStorage.Action.RESPOND, withdrawRequestNonce, true, uint256(1234)); uint256 responseNativeFee = exocoreGateway.quote(clientChainId, withdrawResponsePayload); diff --git a/test/foundry/unit/ExocoreGateway.t.sol b/test/foundry/unit/ExocoreGateway.t.sol index c6c9c24c..e6ac7aff 100644 --- a/test/foundry/unit/ExocoreGateway.t.sol +++ b/test/foundry/unit/ExocoreGateway.t.sol @@ -514,130 +514,64 @@ contract AddWhitelistTokens is SetUp { using stdStorage for StdStorage; using AddressCast for address; - uint256 internal constant TOKEN_ADDRESS_BYTES_LENGTH = 32; + uint256 MESSAGE_LENGTH = 1 + 32; // action + token address as bytes32 + uint256 nativeFee; event WhitelistTokenAdded(uint32 clientChainId, bytes32 token); - bytes32[] whitelistTokens; - uint8[] decimals; - uint256[] tvlLimits; - string[] names; - string[] metaData; + function setUp() public virtual override { + super.setUp(); + nativeFee = exocoreGateway.quote(clientChainId, new bytes(MESSAGE_LENGTH)); + } function test_RevertWhen_CallerNotOwner() public { - _prepareInputs(2); - - uint256 messageLength = TOKEN_ADDRESS_BYTES_LENGTH * whitelistTokens.length + 2; - uint256 nativeFee = exocoreGateway.quote(clientChainId, new bytes(messageLength)); - vm.startPrank(deployer.addr); vm.expectRevert("Ownable: caller is not the owner"); - exocoreGateway.addOrUpdateWhitelistTokens{value: nativeFee}( - clientChainId, whitelistTokens, decimals, tvlLimits, names, metaData + exocoreGateway.addOrUpdateWhitelistToken{value: nativeFee}( + clientChainId, bytes32(0), 18, type(uint256).max, "name", "metadata", "oracleInfo" ); } function test_RevertWhen_Paused() public { vm.startPrank(exocoreValidatorSet.addr); exocoreGateway.pause(); - - _prepareInputs(2); - - uint256 messageLength = TOKEN_ADDRESS_BYTES_LENGTH * whitelistTokens.length + 2; - uint256 nativeFee = exocoreGateway.quote(clientChainId, new bytes(messageLength)); vm.expectRevert("Pausable: paused"); - exocoreGateway.addOrUpdateWhitelistTokens{value: nativeFee}( - clientChainId, whitelistTokens, decimals, tvlLimits, names, metaData - ); - } - - function test_RevertWhen_TokensListTooLong() public { - _prepareInputs(256); - - uint256 messageLength = TOKEN_ADDRESS_BYTES_LENGTH * whitelistTokens.length + 2; - uint256 nativeFee = exocoreGateway.quote(clientChainId, new bytes(messageLength)); - - vm.startPrank(exocoreValidatorSet.addr); - vm.expectRevert(abi.encodeWithSelector(ExocoreGatewayStorage.WhitelistTokensListTooLong.selector)); - exocoreGateway.addOrUpdateWhitelistTokens{value: nativeFee}( - clientChainId, whitelistTokens, decimals, tvlLimits, names, metaData - ); - } - - function test_RevertWhen_LengthNotMatch() public { - _prepareInputs(2); - decimals.push(18); - - uint256 messageLength = TOKEN_ADDRESS_BYTES_LENGTH * whitelistTokens.length + 2; - uint256 nativeFee = exocoreGateway.quote(clientChainId, new bytes(messageLength)); - - vm.startPrank(exocoreValidatorSet.addr); - vm.expectRevert(abi.encodeWithSelector(ExocoreGatewayStorage.InvalidWhitelistTokensInput.selector)); - exocoreGateway.addOrUpdateWhitelistTokens{value: nativeFee}( - clientChainId, whitelistTokens, decimals, tvlLimits, names, metaData + exocoreGateway.addOrUpdateWhitelistToken{value: nativeFee}( + clientChainId, bytes32(0), 18, type(uint256).max, "name", "metadata", "oracleInfo" ); } function test_RevertWhen_HasZeroAddressToken() public { - _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"; - - uint256 messageLength = TOKEN_ADDRESS_BYTES_LENGTH * whitelistTokens.length + 2; - uint256 nativeFee = exocoreGateway.quote(clientChainId, new bytes(messageLength)); - vm.startPrank(exocoreValidatorSet.addr); vm.expectRevert("ExocoreGateway: token cannot be zero address"); - exocoreGateway.addOrUpdateWhitelistTokens{value: nativeFee}( - clientChainId, whitelistTokens, decimals, tvlLimits, names, metaData + exocoreGateway.addOrUpdateWhitelistToken{value: nativeFee}( + clientChainId, bytes32(0), 18, type(uint256).max, "name", "metadata", "oracleInfo" ); } function test_RevertWhen_HasZeroTVMLimit() public { - _prepareInputs(1); - whitelistTokens[0] = bytes32(bytes20(address(restakeToken))); - - uint256 messageLength = TOKEN_ADDRESS_BYTES_LENGTH * 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.addOrUpdateWhitelistTokens{value: nativeFee}( - clientChainId, whitelistTokens, decimals, tvlLimits, names, metaData + exocoreGateway.addOrUpdateWhitelistToken{value: nativeFee}( + clientChainId, bytes32(bytes20(address(restakeToken))), 18, 0, "name", "metadata", "oracleInfo" ); } - 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] = "ERC20 LST token"; - - uint256 messageLength = TOKEN_ADDRESS_BYTES_LENGTH * whitelistTokens.length + 2; - uint256 nativeFee = exocoreGateway.quote(clientChainId, new bytes(messageLength)); - + function test_Success_AddWhiteListToken() public { 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.addOrUpdateWhitelistTokens{value: nativeFee}( - clientChainId, whitelistTokens, decimals, tvlLimits, names, metaData + vm.expectEmit(address(exocoreGateway)); + emit WhitelistTokenAdded(clientChainId, bytes32(bytes20(address(restakeToken)))); + emit MessageSent(GatewayStorage.Action.REQUEST_ADD_WHITELIST_TOKEN, generateUID(1, false), 1, nativeFee); + exocoreGateway.addOrUpdateWhitelistToken{value: nativeFee}( + clientChainId, + bytes32(bytes20(address(restakeToken))), + 18, + 1e8 ether, + "RestakeToken", + "ERC20 LST token", + "oracleInfo" ); - } - - 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); + vm.stopPrank(); } function generateUID(uint64 nonce, bool fromClientChainToExocore) internal view returns (bytes32 uid) { @@ -654,136 +588,113 @@ contract AddWhitelistTokens is SetUp { } -contract UpdateWhitelistTokens is SetUp { +contract UpdateWhitelistTokens is AddWhitelistTokens { - using AddressCast for address; + struct TokenDetails { + bytes32 tokenAddress; + uint8 decimals; + uint256 tvlLimit; + string name; + string metaData; + string oracleInfo; + } - uint256 internal constant TOKEN_ADDRESS_BYTES_LENGTH = 32; + TokenDetails tokenDetails; event WhitelistTokenUpdated(uint32 clientChainId, bytes32 token); - bytes32[] whitelistTokens; - uint8[] decimals; - uint256[] tvlLimits; - string[] names; - string[] metaData; - - function test_RevertWhen_CallerNotOwner() public { - _prepareInputs(2); - + function setUp() public virtual override { + super.setUp(); + tokenDetails = TokenDetails({ + tokenAddress: bytes32(bytes20(address(restakeToken))), + decimals: 18, + tvlLimit: 1e8 ether, + name: "RestakeToken", + metaData: "ERC20 LST token", + oracleInfo: "oracleInfo" + }); + // since we are inheriting add whitelist token, the test + // test_Success_AddWhiteListToken() will run. + // if we add a token to whitelist here, that test will fail. + // hence, we call test_Success_AddWhiteListToken() in each + // test case. + } + + function test_RevertUpdateWhen_CallerNotOwner() public { + test_Success_AddWhiteListToken(); // focus only on updates vm.startPrank(deployer.addr); vm.expectRevert("Ownable: caller is not the owner"); - exocoreGateway.addOrUpdateWhitelistTokens(clientChainId, whitelistTokens, decimals, tvlLimits, names, metaData); + exocoreGateway.addOrUpdateWhitelistToken{value: 0}( + clientChainId, + tokenDetails.tokenAddress, + tokenDetails.decimals, + tokenDetails.tvlLimit, + tokenDetails.name, + tokenDetails.metaData, + tokenDetails.oracleInfo + ); } - function test_RevertWhen_Paused() public { + function test_RevertUpdateWhen_Paused() public { + test_Success_AddWhiteListToken(); // focus only on updates vm.startPrank(exocoreValidatorSet.addr); exocoreGateway.pause(); - - _prepareInputs(2); - vm.expectRevert("Pausable: paused"); - exocoreGateway.addOrUpdateWhitelistTokens(clientChainId, whitelistTokens, decimals, tvlLimits, names, metaData); - } - - function test_RevertWhen_TokensListTooLong() public { - _prepareInputs(256); - - vm.startPrank(exocoreValidatorSet.addr); - vm.expectRevert(abi.encodeWithSelector(ExocoreGatewayStorage.WhitelistTokensListTooLong.selector)); - exocoreGateway.addOrUpdateWhitelistTokens(clientChainId, whitelistTokens, decimals, tvlLimits, names, metaData); - } - - function test_RevertWhen_LengthNotMatch() public { - _prepareInputs(2); - decimals.push(18); - - vm.startPrank(exocoreValidatorSet.addr); - vm.expectRevert(abi.encodeWithSelector(ExocoreGatewayStorage.InvalidWhitelistTokensInput.selector)); - exocoreGateway.addOrUpdateWhitelistTokens(clientChainId, whitelistTokens, decimals, tvlLimits, names, metaData); - } - - function test_RevertWhen_HasZeroAddressToken() public { - _prepareInputs(1); - whitelistTokens[0] = bytes32(bytes20(address(restakeToken))); - decimals[0] = 18; - tvlLimits[0] = 1e8 ether; - names[0] = "RestakeToken"; - 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"); - exocoreGateway.addOrUpdateWhitelistTokens(clientChainId, whitelistTokens, decimals, tvlLimits, names, metaData); + exocoreGateway.addOrUpdateWhitelistToken{value: 0}( + clientChainId, + tokenDetails.tokenAddress, + tokenDetails.decimals, + tokenDetails.tvlLimit, + tokenDetails.name, + tokenDetails.metaData, + tokenDetails.oracleInfo + ); } - 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] = "ERC20 LST token"; - _addWhitelistTokens(clientChainId, whitelistTokens, decimals, tvlLimits, names, metaData); - - tvlLimits[0] = 0; - + function test_RevertUpdateWhen_HasZeroTVMLimit() public { + test_Success_AddWhiteListToken(); // focus only on updates vm.startPrank(exocoreValidatorSet.addr); vm.expectRevert("ExocoreGateway: tvl limit should not be zero"); - exocoreGateway.addOrUpdateWhitelistTokens(clientChainId, whitelistTokens, decimals, tvlLimits, names, metaData); + exocoreGateway.addOrUpdateWhitelistToken{value: 0}( + clientChainId, + tokenDetails.tokenAddress, + tokenDetails.decimals, + 0, + tokenDetails.name, + tokenDetails.metaData, + tokenDetails.oracleInfo + ); } - 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] = "ERC20 LST token"; - - // 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]); + function test_Success_UpdateWhitelistToken() public { + test_Success_AddWhiteListToken(); // focus only on updates vm.startPrank(exocoreValidatorSet.addr); - exocoreGateway.addOrUpdateWhitelistTokens(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); + vm.expectEmit(address(exocoreGateway)); + emit WhitelistTokenUpdated(clientChainId, tokenDetails.tokenAddress); + exocoreGateway.addOrUpdateWhitelistToken{value: 0}( + clientChainId, + tokenDetails.tokenAddress, + tokenDetails.decimals, + tokenDetails.tvlLimit * 5, + tokenDetails.name, + tokenDetails.metaData, + tokenDetails.oracleInfo + ); } - function _addWhitelistTokens( - uint32 clientChainId_, - bytes32[] memory whitelistTokens_, - uint8[] memory decimals_, - uint256[] memory tvlLimits_, - string[] memory names_, - string[] memory metaData_ - ) internal { + function test_RevertUpdate_NonZeroValue() public { + test_Success_AddWhiteListToken(); // focus only on updates vm.startPrank(exocoreValidatorSet.addr); - uint256 messageLength = TOKEN_ADDRESS_BYTES_LENGTH * whitelistTokens.length + 2; - uint256 nativeFee = exocoreGateway.quote(clientChainId, new bytes(messageLength)); - exocoreGateway.addOrUpdateWhitelistTokens{value: nativeFee}( - clientChainId_, whitelistTokens_, decimals_, tvlLimits_, names_, metaData_ + vm.expectRevert(Errors.NonZeroValue.selector); + exocoreGateway.addOrUpdateWhitelistToken{value: nativeFee}( + clientChainId, + tokenDetails.tokenAddress, + tokenDetails.decimals, + tokenDetails.tvlLimit * 5, + tokenDetails.name, + tokenDetails.metaData, + tokenDetails.oracleInfo ); - vm.stopPrank(); } } diff --git a/test/mocks/AssetsMock.sol b/test/mocks/AssetsMock.sol index 29283505..2fdb9037 100644 --- a/test/mocks/AssetsMock.sol +++ b/test/mocks/AssetsMock.sol @@ -71,7 +71,8 @@ contract AssetsMock is IAssets { uint8 decimals, uint256 tvlLimit, string calldata name, - string calldata metaData + string calldata metaData, + string calldata oracleInfo ) external returns (bool success, bool updated) { require(isRegisteredChain[clientChainId], "the chain is not registered before"); diff --git a/test/mocks/ExocoreGatewayMock.sol b/test/mocks/ExocoreGatewayMock.sol index 45b795b7..13af69a3 100644 --- a/test/mocks/ExocoreGatewayMock.sol +++ b/test/mocks/ExocoreGatewayMock.sol @@ -175,73 +175,51 @@ contract ExocoreGatewayMock is super.setPeer(clientChainId, clientChainGateway); } - // Though this function would call precompiled contract, all precompiled contracts belong to Exocore - // and we could make sure its implementation does not have dangerous behavior like reentrancy. - // slither-disable-next-line reentrancy-no-eth - function addOrUpdateWhitelistTokens( + function addOrUpdateWhitelistToken( uint32 clientChainId, - bytes32[] calldata tokens, - uint8[] calldata decimals, - uint256[] calldata tvlLimits, - string[] calldata names, - string[] calldata metaData + bytes32 token, + uint8 decimals, + uint256 tvlLimit, + string calldata name, + string calldata metaData, + string calldata oracleInfo ) external payable onlyOwner whenNotPaused nonReentrant { - _validateWhitelistTokensInput(tokens, decimals, tvlLimits, names, metaData); - - bool success; - bool updated; - for (uint256 i; i < tokens.length; i++) { - require(tokens[i] != bytes32(0), "ExocoreGateway: token cannot be zero address"); - 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"); - - (success, updated) = ASSETS_CONTRACT.registerOrUpdateTokens( - clientChainId, abi.encodePacked(tokens[i]), decimals[i], tvlLimits[i], names[i], metaData[i] - ); + require(token != bytes32(0), "ExocoreGateway: token cannot be zero address"); + require(tvlLimit > 0, "ExocoreGateway: tvl limit should not be zero"); + require(bytes(name).length != 0, "ExocoreGateway: name cannot be empty"); + require(bytes(metaData).length != 0, "ExocoreGateway: meta data cannot be empty"); + + (bool success, bool updated) = ASSETS_CONTRACT.registerOrUpdateTokens( + clientChainId, + abi.encodePacked(token), // convert to bytes from bytes32 + decimals, + tvlLimit, + name, + metaData, + oracleInfo + ); - if (success) { - if (!updated) { - emit WhitelistTokenAdded(clientChainId, tokens[i]); - } else { - emit WhitelistTokenUpdated(clientChainId, tokens[i]); + if (success) { + if (!updated) { + if (msg.value == 0) { + revert Errors.ZeroValue(); } + emit WhitelistTokenAdded(clientChainId, token); + _sendInterchainMsg( + clientChainId, + Action.REQUEST_ADD_WHITELIST_TOKEN, + abi.encodePacked(token), // convert for decoding it on the receiving end + false + ); } else { - if (!updated) { - revert AddWhitelistTokenFailed(tokens[i]); - } else { - revert UpdateWhitelistTokenFailed(tokens[i]); + if (msg.value != 0) { + revert Errors.NonZeroValue(); } + emit WhitelistTokenUpdated(clientChainId, token); } - } - - if (!updated) { - _sendInterchainMsg( - clientChainId, - Action.REQUEST_ADD_WHITELIST_TOKENS, - abi.encodePacked(uint8(tokens.length), tokens), - false - ); - } - } - - function _validateWhitelistTokensInput( - bytes32[] calldata tokens, - uint8[] calldata decimals, - uint256[] calldata tvlLimits, - string[] calldata names, - string[] calldata metaData - ) internal pure { - 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(); + } else { + // if the precompile call didn't succeed, we don't know if it is an update. + revert AddOrUpdateWhitelistTokenFailed(token); } } diff --git a/test/mocks/NonShortCircuitEndpointV2Mock.sol b/test/mocks/NonShortCircuitEndpointV2Mock.sol index b4c2b661..8744ec7f 100644 --- a/test/mocks/NonShortCircuitEndpointV2Mock.sol +++ b/test/mocks/NonShortCircuitEndpointV2Mock.sol @@ -718,9 +718,7 @@ contract NonShortCircuitEndpointV2Mock is ILayerZeroEndpointV2, MessagingContext } else if (optionType == ExecutorOptions.OPTION_TYPE_ORDERED_EXECUTION) { // ordered = true; } - else { - revert IExecutorFeeLib.Executor_UnsupportedOptionType(optionType); - } + else revert IExecutorFeeLib.Executor_UnsupportedOptionType(optionType); } if (cursor != _options.length) {