diff --git a/src/core/ExocoreGateway.sol b/src/core/ExocoreGateway.sol index 26e6ff1b..10625df4 100644 --- a/src/core/ExocoreGateway.sol +++ b/src/core/ExocoreGateway.sol @@ -553,17 +553,19 @@ contract ExocoreGateway is function _buildOptions(uint32 srcChainId, Action act) private pure returns (bytes memory) { bytes memory options = OptionsBuilder.newOptions(); - if (srcChainId == SOLANA_DEVNET_CHAIN_ID || srcChainId == SOLANA_MAINNET_CHAIN_ID) { - if (act == Action.REQUEST_ADD_WHITELIST_TOKEN) { - options = options.addExecutorLzReceiveOption(DESTINATION_GAS_LIMIT, SOLANA_WHITELIST_TOKEN_MSG_VALUE); - } else { - options = options.addExecutorLzReceiveOption(DESTINATION_GAS_LIMIT, DESTINATION_MSG_VALUE); - } - } else { - options = options.addExecutorLzReceiveOption(DESTINATION_GAS_LIMIT, DESTINATION_MSG_VALUE) - .addExecutorOrderedExecutionOption(); + bool isSolana = srcChainId == SOLANA_DEVNET_CHAIN_ID || srcChainId == SOLANA_MAINNET_CHAIN_ID; + + if (!isSolana) { + // currently, LZ does not support ordered execution for Solana + options = options.addExecutorOrderedExecutionOption(); } + uint128 value = isSolana && act == Action.REQUEST_ADD_WHITELIST_TOKEN + ? SOLANA_WHITELIST_TOKEN_MSG_VALUE + : DESTINATION_MSG_VALUE; + + options = options.addExecutorLzReceiveOption(DESTINATION_GAS_LIMIT, value); + return options; } diff --git a/test/foundry/unit/ExocoreGateway.t.sol b/test/foundry/unit/ExocoreGateway.t.sol index 3ad8eab2..be79d4db 100644 --- a/test/foundry/unit/ExocoreGateway.t.sol +++ b/test/foundry/unit/ExocoreGateway.t.sol @@ -39,12 +39,17 @@ contract SetUp is Test { ExocoreGateway exocoreGateway; ClientChainGateway clientGateway; + ClientChainGateway solanaClientGateway; + NonShortCircuitEndpointV2Mock exocoreLzEndpoint; NonShortCircuitEndpointV2Mock clientLzEndpoint; + NonShortCircuitEndpointV2Mock solanaClientLzEndpoint; + ERC20 restakeToken; uint16 exocoreChainId = 1; uint16 clientChainId = 2; + uint16 solanaClientChainId = 40_168; struct Player { uint256 privateKey; @@ -64,6 +69,7 @@ contract SetUp is Test { deployer = Player({privateKey: uint256(0xb), addr: vm.addr(uint256(0xb))}); withdrawer = Player({privateKey: uint256(0xc), addr: vm.addr(uint256(0xb))}); clientGateway = ClientChainGateway(payable(address(0xd))); + solanaClientGateway = ClientChainGateway(payable(address(0xe))); // bind precompile mock contracts code to constant precompile address bytes memory AssetsMockCode = vm.getDeployedCode("AssetsMock.sol"); @@ -88,6 +94,7 @@ contract SetUp is Test { exocoreLzEndpoint = new NonShortCircuitEndpointV2Mock(exocoreChainId, exocoreValidatorSet.addr); clientLzEndpoint = new NonShortCircuitEndpointV2Mock(clientChainId, exocoreValidatorSet.addr); + solanaClientLzEndpoint = new NonShortCircuitEndpointV2Mock(solanaClientChainId, exocoreValidatorSet.addr); ProxyAdmin proxyAdmin = new ProxyAdmin(); ExocoreGateway exocoreGatewayLogic = new ExocoreGateway(address(exocoreLzEndpoint)); @@ -108,6 +115,17 @@ contract SetUp is Test { "EVM compatible client chain", "secp256k1" ); + + exocoreLzEndpoint.setDestLzEndpoint(address(solanaClientGateway), address(clientLzEndpoint)); + exocoreGateway.registerOrUpdateClientChain( + solanaClientChainId, + address(solanaClientGateway).toBytes32(), + 20, + "solanaClientChain", + "Non-EVM compatible client chain", + "ed25519" + ); + vm.stopPrank(); // transfer some gas fee to exocore gateway as it has to pay for the relay fee to layerzero endpoint when @@ -116,14 +134,38 @@ contract SetUp is Test { } function generateUID(uint64 nonce, bool fromClientChainToExocore) internal view returns (bytes32 uid) { + uid = generateUID(nonce, fromClientChainToExocore, true); + } + + function generateUID(uint64 nonce, bool fromClientChainToExocore, bool isEVM) internal view returns (bytes32 uid) { if (fromClientChainToExocore) { - uid = GUID.generate( - nonce, clientChainId, address(clientGateway), exocoreChainId, address(exocoreGateway).toBytes32() - ); + if (isEVM) { + uid = GUID.generate( + nonce, clientChainId, address(clientGateway), exocoreChainId, address(exocoreGateway).toBytes32() + ); + } else { + uid = GUID.generate( + nonce, + solanaClientChainId, + address(solanaClientGateway), + exocoreChainId, + address(exocoreGateway).toBytes32() + ); + } } else { - uid = GUID.generate( - nonce, exocoreChainId, address(exocoreGateway), clientChainId, address(clientGateway).toBytes32() - ); + if (isEVM) { + uid = GUID.generate( + nonce, exocoreChainId, address(exocoreGateway), clientChainId, address(clientGateway).toBytes32() + ); + } else { + uid = GUID.generate( + nonce, + exocoreChainId, + address(exocoreGateway), + solanaClientChainId, + address(solanaClientGateway).toBytes32() + ); + } } } @@ -526,6 +568,7 @@ contract AddWhitelistTokens is SetUp { uint256 MESSAGE_LENGTH = 1 + 32 + 16; // action + token address as bytes32 + uint128 tvl limit uint256 nativeFee; + uint256 nativeFeeForSolana; error IncorrectNativeFee(uint256 amount); @@ -534,6 +577,9 @@ contract AddWhitelistTokens is SetUp { function setUp() public virtual override { super.setUp(); nativeFee = exocoreGateway.quote(clientChainId, new bytes(MESSAGE_LENGTH)); + bytes memory message = new bytes(MESSAGE_LENGTH); + message[0] = bytes1(abi.encodePacked(Action.REQUEST_ADD_WHITELIST_TOKEN)); + nativeFeeForSolana = exocoreGateway.quote(solanaClientChainId, message); } function test_RevertWhen_CallerNotOwner() public { @@ -587,6 +633,24 @@ contract AddWhitelistTokens is SetUp { vm.stopPrank(); } + function test_Success_AddWhiteListTokenOnSolana() public { + vm.startPrank(exocoreValidatorSet.addr); + vm.expectEmit(address(exocoreGateway)); + emit WhitelistTokenAdded(solanaClientChainId, bytes32(bytes20(address(restakeToken)))); + vm.expectEmit(address(exocoreGateway)); + emit MessageSent(Action.REQUEST_ADD_WHITELIST_TOKEN, generateUID(1, false, false), 1, nativeFeeForSolana); + exocoreGateway.addWhitelistToken{value: nativeFeeForSolana}( + solanaClientChainId, + bytes32(bytes20(address(restakeToken))), + 9, + "RestakeToken", + "Spl LST token", + "oracleInfo", + 5000 * 1e9 + ); + vm.stopPrank(); + } + } contract UpdateWhitelistTokens is SetUp {