From e19817b2840ebadf106d4d3b417223318200fe3c Mon Sep 17 00:00:00 2001 From: juan <0xjuaan@gmail.com> Date: Sun, 15 Mar 2026 01:09:41 +0800 Subject: [PATCH 1/3] feat: use sendAsset instead of spotSend for bridgeToEvm Replace spotSend (action 6) with sendAsset (action 13) in bridgeToEvm, using token-specific system addresses. Add sendAsset support to the HyperCore simulator and a BridgeToEvmTest contract with deploy script. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/CoreWriterLib.sol | 13 +++++++++---- test/simulation/HyperCore.sol | 5 +++++ test/simulation/hyper-core/CoreExecution.sol | 15 +++++++++++++++ test/simulation/hyper-core/CoreState.sol | 9 +++++++++ 4 files changed, 38 insertions(+), 4 deletions(-) diff --git a/src/CoreWriterLib.sol b/src/CoreWriterLib.sol index e01270f..d1d9fe7 100644 --- a/src/CoreWriterLib.sol +++ b/src/CoreWriterLib.sol @@ -86,10 +86,8 @@ library CoreWriterLib { bridgeToEvm(tokenIndex, evmAmount, true); } - // NOTE: For bridging non-HYPE tokens, the contract must hold some HYPE on core (enough to cover the transfer gas), otherwise spotSend will fail + // NOTE: For bridging non-HYPE tokens, the contract must hold some HYPE on core (enough to cover the transfer gas), otherwise sendAsset will fail function bridgeToEvm(uint64 token, uint256 amount, bool isEvmAmount) internal { - address systemAddress = getSystemAddress(token); - uint64 coreAmount; if (isEvmAmount) { coreAmount = HLConversions.evmToWei(token, amount); @@ -99,7 +97,14 @@ library CoreWriterLib { coreAmount = uint64(amount); } - spotSend(systemAddress, token, coreAmount); + sendAsset( + getSystemAddress(token), + address(0), + HLConstants.SPOT_DEX, + HLConstants.SPOT_DEX, + token, + coreAmount + ); } function spotSend(address to, uint64 token, uint64 amountWei) internal { diff --git a/test/simulation/HyperCore.sol b/test/simulation/HyperCore.sol index 8a01daa..4c04a56 100644 --- a/test/simulation/HyperCore.sol +++ b/test/simulation/HyperCore.sol @@ -46,6 +46,11 @@ contract HyperCore is CoreExecution { return; } + if (kind == HLConstants.SEND_ASSET_ACTION) { + executeSendAsset(sender, abi.decode(data, (SendAssetAction))); + return; + } + if (kind == HLConstants.USD_CLASS_TRANSFER_ACTION) { executeUsdClassTransfer(sender, abi.decode(data, (UsdClassTransferAction))); return; diff --git a/test/simulation/hyper-core/CoreExecution.sol b/test/simulation/hyper-core/CoreExecution.sol index 0cd51a1..26ae167 100644 --- a/test/simulation/hyper-core/CoreExecution.sol +++ b/test/simulation/hyper-core/CoreExecution.sol @@ -446,6 +446,21 @@ contract CoreExecution is CoreView { } } + function executeSendAsset(address sender, SendAssetAction memory action) public { + require( + action.source_dex == HLConstants.SPOT_DEX && action.destination_dex == HLConstants.SPOT_DEX, + "only SPOT_DEX supported in simulator" + ); + + // When destination is BASE_SYSTEM_ADDRESS, map to token-specific system address for EVM bridging + address destination = action.destination; + if (destination == address(HLConstants.BASE_SYSTEM_ADDRESS)) { + destination = CoreWriterLib.getSystemAddress(action.token); + } + + executeSpotSend(sender, SpotSendAction(destination, action.token, action.amountWei)); + } + function executeUsdClassTransfer(address sender, UsdClassTransferAction memory action) public initAccountWithToken(sender, USDC_TOKEN_INDEX) diff --git a/test/simulation/hyper-core/CoreState.sol b/test/simulation/hyper-core/CoreState.sol index f8611b8..cb2071d 100644 --- a/test/simulation/hyper-core/CoreState.sol +++ b/test/simulation/hyper-core/CoreState.sol @@ -452,6 +452,15 @@ contract CoreState is StdCheats { uint64 _wei; } + struct SendAssetAction { + address destination; + address subAccount; + uint32 source_dex; + uint32 destination_dex; + uint64 token; + uint64 amountWei; + } + struct UsdClassTransferAction { uint64 ntl; bool toPerp; From c8d1ce37585610562e73aa4d0a3ee7c461358c71 Mon Sep 17 00:00:00 2001 From: juan <0xjuaan@gmail.com> Date: Sun, 15 Mar 2026 01:09:59 +0800 Subject: [PATCH 2/3] add BridgeToEvmTest contract and deploy script Co-Authored-By: Claude Opus 4.6 (1M context) --- script/DeployBridgeToEvmTest.s.sol | 75 ++++++++++++++++++++++++++++++ src/examples/BridgeToEvmTest.sol | 44 ++++++++++++++++++ 2 files changed, 119 insertions(+) create mode 100644 script/DeployBridgeToEvmTest.s.sol create mode 100644 src/examples/BridgeToEvmTest.sol diff --git a/script/DeployBridgeToEvmTest.s.sol b/script/DeployBridgeToEvmTest.s.sol new file mode 100644 index 0000000..e871246 --- /dev/null +++ b/script/DeployBridgeToEvmTest.s.sol @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {Script, console} from "forge-std/Script.sol"; +import {BridgeToEvmTest} from "../src/examples/BridgeToEvmTest.sol"; + +contract DeployBridgeToEvmTest is Script { + function run() public { + uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); + + vm.startBroadcast(deployerPrivateKey); + + // Deploy with 0.01 HYPE so contract has funds to bridge + BridgeToEvmTest testContract = new BridgeToEvmTest{value: 0.01 ether}(); + + console.log("BridgeToEvmTest deployed at:", address(testContract)); + console.log("Owner:", testContract.owner()); + + vm.stopBroadcast(); + } +} + +contract BridgeHypeToCore is Script { + function run() public { + uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); + address contractAddr = vm.envAddress("CONTRACT"); + + vm.startBroadcast(deployerPrivateKey); + + BridgeToEvmTest testContract = BridgeToEvmTest(payable(contractAddr)); + + // Bridge 0.01 HYPE to Core (token index 150) + testContract.bridgeToCore{value: 0.01 ether}(150, 0.01 ether); + + console.log("Bridged 0.01 HYPE to Core"); + + vm.stopBroadcast(); + } +} + +contract BridgeHypeToEvm is Script { + function run() public { + uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); + address contractAddr = vm.envAddress("CONTRACT"); + + vm.startBroadcast(deployerPrivateKey); + + BridgeToEvmTest testContract = BridgeToEvmTest(payable(contractAddr)); + + // Bridge 0.01 HYPE back to EVM + testContract.bridgeToEvm(150, 0.01 ether); + + console.log("Bridged 0.01 HYPE back to EVM"); + + vm.stopBroadcast(); + } +} + +contract WithdrawFunds is Script { + function run() public { + uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); + address contractAddr = vm.envAddress("CONTRACT"); + + vm.startBroadcast(deployerPrivateKey); + + BridgeToEvmTest testContract = BridgeToEvmTest(payable(contractAddr)); + + // Withdraw all ETH/HYPE back to owner + testContract.withdrawETH(); + + console.log("Withdrawn all HYPE to owner"); + + vm.stopBroadcast(); + } +} diff --git a/src/examples/BridgeToEvmTest.sol b/src/examples/BridgeToEvmTest.sol new file mode 100644 index 0000000..c0d8a85 --- /dev/null +++ b/src/examples/BridgeToEvmTest.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import {CoreWriterLib, HLConstants} from "@hyper-evm-lib/src/CoreWriterLib.sol"; + +contract BridgeToEvmTest { + using SafeERC20 for IERC20; + + address public immutable owner; + + modifier onlyOwner() { + require(msg.sender == owner, "not owner"); + _; + } + + constructor() payable { + owner = msg.sender; + } + + function bridgeToCore(uint64 token, uint256 evmAmount) external payable onlyOwner { + CoreWriterLib.bridgeToCore(token, evmAmount); + } + + function bridgeToEvm(uint64 token, uint256 evmAmount) external onlyOwner { + CoreWriterLib.bridgeToEvm(token, evmAmount, true); + } + + function bridgeToEvmCoreAmount(uint64 token, uint64 coreAmount) external onlyOwner { + CoreWriterLib.bridgeToEvm(token, coreAmount, false); + } + + function withdrawERC20(address token, uint256 amount) external onlyOwner { + IERC20(token).safeTransfer(owner, amount); + } + + function withdrawETH() external onlyOwner { + (bool success,) = owner.call{value: address(this).balance}(""); + require(success, "ETH transfer failed"); + } + + receive() external payable {} +} From 27c1be20d2ac46878222368a5aa241edb86b7190 Mon Sep 17 00:00:00 2001 From: Juan <122077337+0xjuaan@users.noreply.github.com> Date: Sun, 15 Mar 2026 01:30:10 +0800 Subject: [PATCH 3/3] Delete script/DeployBridgeToEvmTest.s.sol --- script/DeployBridgeToEvmTest.s.sol | 75 ------------------------------ 1 file changed, 75 deletions(-) delete mode 100644 script/DeployBridgeToEvmTest.s.sol diff --git a/script/DeployBridgeToEvmTest.s.sol b/script/DeployBridgeToEvmTest.s.sol deleted file mode 100644 index e871246..0000000 --- a/script/DeployBridgeToEvmTest.s.sol +++ /dev/null @@ -1,75 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import {Script, console} from "forge-std/Script.sol"; -import {BridgeToEvmTest} from "../src/examples/BridgeToEvmTest.sol"; - -contract DeployBridgeToEvmTest is Script { - function run() public { - uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); - - vm.startBroadcast(deployerPrivateKey); - - // Deploy with 0.01 HYPE so contract has funds to bridge - BridgeToEvmTest testContract = new BridgeToEvmTest{value: 0.01 ether}(); - - console.log("BridgeToEvmTest deployed at:", address(testContract)); - console.log("Owner:", testContract.owner()); - - vm.stopBroadcast(); - } -} - -contract BridgeHypeToCore is Script { - function run() public { - uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); - address contractAddr = vm.envAddress("CONTRACT"); - - vm.startBroadcast(deployerPrivateKey); - - BridgeToEvmTest testContract = BridgeToEvmTest(payable(contractAddr)); - - // Bridge 0.01 HYPE to Core (token index 150) - testContract.bridgeToCore{value: 0.01 ether}(150, 0.01 ether); - - console.log("Bridged 0.01 HYPE to Core"); - - vm.stopBroadcast(); - } -} - -contract BridgeHypeToEvm is Script { - function run() public { - uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); - address contractAddr = vm.envAddress("CONTRACT"); - - vm.startBroadcast(deployerPrivateKey); - - BridgeToEvmTest testContract = BridgeToEvmTest(payable(contractAddr)); - - // Bridge 0.01 HYPE back to EVM - testContract.bridgeToEvm(150, 0.01 ether); - - console.log("Bridged 0.01 HYPE back to EVM"); - - vm.stopBroadcast(); - } -} - -contract WithdrawFunds is Script { - function run() public { - uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); - address contractAddr = vm.envAddress("CONTRACT"); - - vm.startBroadcast(deployerPrivateKey); - - BridgeToEvmTest testContract = BridgeToEvmTest(payable(contractAddr)); - - // Withdraw all ETH/HYPE back to owner - testContract.withdrawETH(); - - console.log("Withdrawn all HYPE to owner"); - - vm.stopBroadcast(); - } -}