From 0bbed634ce2fd9350c4bfcfae74aec828d14247d Mon Sep 17 00:00:00 2001 From: Taylor Webb Date: Tue, 14 Oct 2025 15:11:07 -0600 Subject: [PATCH 01/16] HyperCoreLib init commit --- contracts/libraries/HyperCoreLib.sol | 267 +++++++++++++++++++++++++++ 1 file changed, 267 insertions(+) create mode 100644 contracts/libraries/HyperCoreLib.sol diff --git a/contracts/libraries/HyperCoreLib.sol b/contracts/libraries/HyperCoreLib.sol new file mode 100644 index 000000000..68e8f61fd --- /dev/null +++ b/contracts/libraries/HyperCoreLib.sol @@ -0,0 +1,267 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; + +// TODO: handle MIT / BUSL license + +interface ICoreWriter { + function sendRawAction(bytes calldata data) external; +} + +library HyperCoreLib { + using SafeERC20 for IERC20; + + struct HyperAssetAmount { + uint256 evm; + uint64 core; + uint64 coreBalanceAssetBridge; + } + + struct LimitOrder { + uint32 asset; + bool isBuy; + uint64 limitPx1e8; // price scaled by 1e8 + uint64 sz1e8; // size scaled by 1e8 + bool reduceOnly; + uint8 encodedTif; // 1 = ALO, 2 = GTC, 3 = IOC + uint128 cloid; // 0 => no client order id + } + + struct SpotBalance { + uint64 total; + uint64 hold; // Unused in this implementation + uint64 entryNtl; // Unused in this implementation + } + + struct CoreUserExists { + bool exists; + } + + address public constant SPOT_BALANCE_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000000801; + address public constant CORE_USER_EXISTS_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000000810; + address public constant CORE_WRITER_PRECOMPILE_ADDRESS = 0x3333333333333333333333333333333333333333; + address public constant BASE_ASSET_BRIDGE_ADDRESS = 0x2000000000000000000000000000000000000000; + uint256 public constant BASE_ASSET_BRIDGE_ADDRESS_UINT256 = uint256(uint160(BASE_ASSET_BRIDGE_ADDRESS)); + bytes4 public constant SPOT_SEND_HEADER = 0x01000006; + + error TransferAmtExceedsAssetBridgeBalance(uint256 amt, uint256 maxAmt); + error SpotBalancePrecompileCallFailed(); + error CoreUserExistsPrecompileCallFailed(); + error CoreUserNotActivated(); + + /** + * @notice Transfer `amountEVMDecimals` of `erc20` from HyperEVM to `toHCAccount` on HyperCore. + * @dev Returns the amount credited on Core in Core units (post conversion). + */ + function transferERC20ToHyperCore( + address erc20, + uint256 amountEVMDecimals, + address to, + uint64 erc20HCIndex, + int8 decimalDiff + ) internal returns (uint64 coreAmount) { + // if the transfer amount exceeds the bridge balance, this wil revert + HyperAssetAmount memory amounts = quoteHyperCoreAmount( + erc20HCIndex, + decimalDiff, + into_assetBridgeAddress(erc20HCIndex), + amountEVMDecimals + ); + + // TODO: removed check if user is not activated on HyperCore + + if (amounts.evm != 0) { + // Transfer the tokens to this contract's address on HyperCore + IERC20(erc20).safeTransfer(into_assetBridgeAddress(erc20HCIndex), amounts.evm); + + // Transfer the tokens from this contract on HyperCore to the `to` address on HyperCore + _submitCoreWriterTransfer(to, erc20HCIndex, amounts.core); + } + + return amounts.core; + } + + /** + * @notice Transfers tokens on HyperCore using the CoreWriter precompile + * @param _to The address to receive tokens on HyperCore + * @param _coreIndex The core index of the token + * @param _coreAmount The amount to transfer on HyperCore + */ + function _submitCoreWriterTransfer(address _to, uint64 _coreIndex, uint64 _coreAmount) internal { + bytes memory action = abi.encode(_to, _coreIndex, _coreAmount); + bytes memory payload = abi.encodePacked(SPOT_SEND_HEADER, action); + /// Transfers HYPE tokens from the composer address on HyperCore to the _to via the SpotSend precompile + ICoreWriter(CORE_WRITER_PRECOMPILE_ADDRESS).sendRawAction(payload); + } + + // /** + // * @notice Checks if the receiver's address is activated on HyperCore + // * @notice To be overriden on FeeToken or other implementations since this can be used to activate tokens + // * @dev Default behavior is to revert if the user's account is NOT activated + // * @param _to The address to check + // * @param _coreAmount The core amount to transfer + // * @return The final core amount to transfer (same as _coreAmount in default impl) + // */ + // // TODO: clean this up, don't need this function probably + // function _getFinalCoreAmount(address _to, uint64 _coreAmount) internal view returns (uint64) { + // if (!coreUserExists(_to)) revert CoreUserNotActivated(); + // return _coreAmount; + // } + + /** + * @notice Transfer `amountHCDecimals` of `erc20` from this contract on HyperCore to `to` on HyperCore. + */ + function transferERC20OnHC(address to, uint64 erc20HCIndex, uint64 amountHCDecimals) internal {} + + /** + * @notice Enqueue a limit order on HyperCore. + */ + function enqueueLimitOrder(LimitOrder calldata order) internal {} + + /** + * @notice Quotes the conversion of evm tokens to hypercore tokens + * @param _coreIndexId The core index id of the token to transfer + * @param _decimalDiff The decimal difference of evmDecimals - coreDecimals + * @param _bridgeAddress The asset bridge address of the token to transfer + * @param _amountLD The number of tokens that the composer received (pre-dusted) that we are trying to send + * @return IHyperAssetAmount - The amount of tokens to send to HyperCore (scaled on evm), dust (to be refunded), and the swap amount (of the tokens scaled on hypercore) + */ + function quoteHyperCoreAmount( + uint64 _coreIndexId, + int8 _decimalDiff, + address _bridgeAddress, + uint256 _amountLD + ) internal view returns (HyperAssetAmount memory) { + return into_hyperAssetAmount(_amountLD, spotBalance(_bridgeAddress, _coreIndexId), _decimalDiff); + } + + /** + * @notice Get the balance of the specified ERC20 for `account` on HyperCore. + */ + function spotBalance(address account, uint64 token) internal view returns (uint64 balance) { + (bool success, bytes memory result) = SPOT_BALANCE_PRECOMPILE_ADDRESS.staticcall(abi.encode(account, token)); + if (!success) revert SpotBalancePrecompileCallFailed(); + SpotBalance memory _spotBalance = abi.decode(result, (SpotBalance)); + return _spotBalance.total; + } + + /** + * @notice Converts a core index id to an asset bridge address + * @notice This function is called by the HyperLiquidComposer contract + * @param _coreIndexId The core index id to convert + * @return _assetBridgeAddress The asset bridge address + */ + function into_assetBridgeAddress(uint64 _coreIndexId) internal pure returns (address) { + return address(uint160(BASE_ASSET_BRIDGE_ADDRESS_UINT256 + _coreIndexId)); + } + + /** + * @notice Converts an asset bridge address to a core index id + * @param _assetBridgeAddress The asset bridge address to convert + * @return _coreIndexId The core index id + */ + function into_tokenId(address _assetBridgeAddress) internal pure returns (uint64) { + return uint64(uint160(_assetBridgeAddress) - BASE_ASSET_BRIDGE_ADDRESS_UINT256); + } + + /** + * @notice Converts an amount and an asset to a evm amount and core amount + * @notice This function is called by the HyperLiquidComposer contract + * @param _amount The amount to convert + * @param _assetBridgeSupply The maximum amount transferable capped by the number of tokens located on the HyperCore's side of the asset bridge + * @param _decimalDiff The decimal difference of evmDecimals - coreDecimals + * @return HyperAssetAmount memory - The evm amount and core amount + */ + function into_hyperAssetAmount( + uint256 _amount, + uint64 _assetBridgeSupply, + int8 _decimalDiff + ) internal pure returns (HyperAssetAmount memory) { + uint256 amountEVM; + uint64 amountCore; + + /// @dev HyperLiquid decimal conversion: Scale EVM (u256,evmDecimals) -> Core (u64,coreDecimals) + /// @dev Core amount is guaranteed to be within u64 range. + if (_decimalDiff > 0) { + (amountEVM, amountCore) = into_hyperAssetAmount_decimal_difference_gt_zero( + _amount, + _assetBridgeSupply, + uint8(_decimalDiff) + ); + } else { + (amountEVM, amountCore) = into_hyperAssetAmount_decimal_difference_leq_zero( + _amount, + _assetBridgeSupply, + uint8(-1 * _decimalDiff) + ); + } + + return HyperAssetAmount({ evm: amountEVM, core: amountCore, coreBalanceAssetBridge: _assetBridgeSupply }); + } + + /** + * @notice Computes hyperAssetAmount when EVM decimals > Core decimals + * @notice Reverts if the transfers amount exceeds the asset bridge balance + * @param _amount The amount to convert + * @param _maxTransferableCoreAmount The maximum transferrable amount capped by the asset bridge has range [0,u64.max] + * @param _decimalDiff The decimal difference between HyperEVM and HyperCore + * @return amountEVM The EVM amount + * @return amountCore The core amount + */ + function into_hyperAssetAmount_decimal_difference_gt_zero( + uint256 _amount, + uint64 _maxTransferableCoreAmount, + uint8 _decimalDiff + ) internal pure returns (uint256 amountEVM, uint64 amountCore) { + uint256 scale = 10 ** _decimalDiff; + uint256 maxAmt = _maxTransferableCoreAmount * scale; + + unchecked { + /// @dev Strip out dust from _amount so that _amount and maxEvmAmountFromCoreMax have a maximum of _decimalDiff starting 0s + amountEVM = _amount - (_amount % scale); // Safe: dustAmt = _amount % scale, so dust <= _amount + + if (amountEVM > maxAmt) revert TransferAmtExceedsAssetBridgeBalance(amountEVM, maxAmt); + + /// @dev Safe: Guaranteed to be in the range of [0, u64.max] because it is upperbounded by uint64 maxAmt + amountCore = uint64(amountEVM / scale); + } + } + + /** + * @notice Computes hyperAssetAmount when EVM decimals < Core decimals and 0 + * @notice Reverts if the transfers amount exceeds the asset bridge balance + * @param _amount The amount to convert + * @param _maxTransferableCoreAmount The maximum transferrable amount capped by the asset bridge + * @param _decimalDiff The decimal difference between HyperEVM and HyperCore + * @return amountEVM The EVM amount + * @return amountCore The core amount + */ + function into_hyperAssetAmount_decimal_difference_leq_zero( + uint256 _amount, + uint64 _maxTransferableCoreAmount, + uint8 _decimalDiff + ) internal pure returns (uint256 amountEVM, uint64 amountCore) { + uint256 scale = 10 ** _decimalDiff; + uint256 maxAmt = _maxTransferableCoreAmount / scale; + + unchecked { + amountEVM = _amount; + + /// @dev When `Core > EVM` there will be no opening dust to strip out since all tokens in evm can be represented on core + /// @dev Safe: Bound amountEvm to the range of [0, evmscaled u64.max] + if (_amount > maxAmt) revert TransferAmtExceedsAssetBridgeBalance(amountEVM, maxAmt); + + /// @dev Safe: Guaranteed to be in the range of [0, u64.max] because it is upperbounded by uint64 maxAmt + amountCore = uint64(amountEVM * scale); + } + } + + function coreUserExists(address user) internal view returns (bool) { + (bool success, bytes memory result) = CORE_USER_EXISTS_PRECOMPILE_ADDRESS.staticcall(abi.encode(user)); + if (!success) revert CoreUserExistsPrecompileCallFailed(); + CoreUserExists memory _coreUserExists = abi.decode(result, (CoreUserExists)); + return _coreUserExists.exists; + } +} From 007d7cd0f7999b1aafcfcc2e261a077ccff7d933 Mon Sep 17 00:00:00 2001 From: Taylor Webb Date: Tue, 14 Oct 2025 17:05:02 -0600 Subject: [PATCH 02/16] add functions for submitting and canceling limit orders --- contracts/libraries/HyperCoreLib.sol | 131 ++++++++++++++++----------- 1 file changed, 79 insertions(+), 52 deletions(-) diff --git a/contracts/libraries/HyperCoreLib.sol b/contracts/libraries/HyperCoreLib.sol index 68e8f61fd..1c8a9f146 100644 --- a/contracts/libraries/HyperCoreLib.sol +++ b/contracts/libraries/HyperCoreLib.sol @@ -5,6 +5,8 @@ import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; // TODO: handle MIT / BUSL license +// Note: +// This library does not check if token recipient is activated on HyperCore interface ICoreWriter { function sendRawAction(bytes calldata data) external; @@ -19,16 +21,6 @@ library HyperCoreLib { uint64 coreBalanceAssetBridge; } - struct LimitOrder { - uint32 asset; - bool isBuy; - uint64 limitPx1e8; // price scaled by 1e8 - uint64 sz1e8; // size scaled by 1e8 - bool reduceOnly; - uint8 encodedTif; // 1 = ALO, 2 = GTC, 3 = IOC - uint128 cloid; // 0 => no client order id - } - struct SpotBalance { uint64 total; uint64 hold; // Unused in this implementation @@ -39,17 +31,26 @@ library HyperCoreLib { bool exists; } + // Precompile addresses address public constant SPOT_BALANCE_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000000801; address public constant CORE_USER_EXISTS_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000000810; address public constant CORE_WRITER_PRECOMPILE_ADDRESS = 0x3333333333333333333333333333333333333333; + + // CoreWriter action headers + bytes4 public constant LIMIT_ORDER_HEADER = 0x01000001; // version=1, action=1 + bytes4 public constant SPOT_SEND_HEADER = 0x01000006; // version=1, action=6 + bytes4 public constant CANCEL_BY_CLOID_HEADER = 0x0100000B; // version=1, action=11 + + // Base asset bridge addresses address public constant BASE_ASSET_BRIDGE_ADDRESS = 0x2000000000000000000000000000000000000000; uint256 public constant BASE_ASSET_BRIDGE_ADDRESS_UINT256 = uint256(uint160(BASE_ASSET_BRIDGE_ADDRESS)); - bytes4 public constant SPOT_SEND_HEADER = 0x01000006; error TransferAmtExceedsAssetBridgeBalance(uint256 amt, uint256 maxAmt); error SpotBalancePrecompileCallFailed(); error CoreUserExistsPrecompileCallFailed(); - error CoreUserNotActivated(); + error LimitPxIsZero(); + error OrderSizeIsZero(); + error InvalidTif(); /** * @notice Transfer `amountEVMDecimals` of `erc20` from HyperEVM to `toHCAccount` on HyperCore. @@ -70,63 +71,88 @@ library HyperCoreLib { amountEVMDecimals ); - // TODO: removed check if user is not activated on HyperCore - if (amounts.evm != 0) { // Transfer the tokens to this contract's address on HyperCore IERC20(erc20).safeTransfer(into_assetBridgeAddress(erc20HCIndex), amounts.evm); // Transfer the tokens from this contract on HyperCore to the `to` address on HyperCore - _submitCoreWriterTransfer(to, erc20HCIndex, amounts.core); + transferERC20OnHyperCore(erc20HCIndex, to, amounts.core); + + return amounts.core; } - return amounts.core; + return 0; } /** - * @notice Transfers tokens on HyperCore using the CoreWriter precompile - * @param _to The address to receive tokens on HyperCore - * @param _coreIndex The core index of the token - * @param _coreAmount The amount to transfer on HyperCore + * @notice Transfers tokens from this contract on HyperCore to + * @notice the `to` address on HyperCore using the CoreWriter precompile + * @param erc20CoreIndex The core index of the token + * @param to The address to receive tokens on HyperCore + * @param coreAmount The amount to transfer on HyperCore */ - function _submitCoreWriterTransfer(address _to, uint64 _coreIndex, uint64 _coreAmount) internal { - bytes memory action = abi.encode(_to, _coreIndex, _coreAmount); + function transferERC20OnHyperCore(uint64 erc20CoreIndex, address to, uint64 coreAmount) internal { + bytes memory action = abi.encode(to, erc20CoreIndex, coreAmount); bytes memory payload = abi.encodePacked(SPOT_SEND_HEADER, action); - /// Transfers HYPE tokens from the composer address on HyperCore to the _to via the SpotSend precompile + ICoreWriter(CORE_WRITER_PRECOMPILE_ADDRESS).sendRawAction(payload); } - // /** - // * @notice Checks if the receiver's address is activated on HyperCore - // * @notice To be overriden on FeeToken or other implementations since this can be used to activate tokens - // * @dev Default behavior is to revert if the user's account is NOT activated - // * @param _to The address to check - // * @param _coreAmount The core amount to transfer - // * @return The final core amount to transfer (same as _coreAmount in default impl) - // */ - // // TODO: clean this up, don't need this function probably - // function _getFinalCoreAmount(address _to, uint64 _coreAmount) internal view returns (uint64) { - // if (!coreUserExists(_to)) revert CoreUserNotActivated(); - // return _coreAmount; - // } - /** - * @notice Transfer `amountHCDecimals` of `erc20` from this contract on HyperCore to `to` on HyperCore. + * @notice Submit a limit order on HyperCore. + * @dev Expects price & size already scaled by 1e8 per HyperCore spec. + * @param asset The asset index of the order + * @param isBuy Whether the order is a buy order + * @param limitPriceX1e8 The limit price of the order scaled by 1e8 + * @param sizeX1e8 The size of the order scaled by 1e8 + * @param reduceOnly If true, only reduce existing position rather than opening a new opposing order + * @param encodedTif Time-in-Force: 1 = ALO, 2 = GTC, 3 = IOC + * @param cloid The client order id of the order, 0 means no cloid */ - function transferERC20OnHC(address to, uint64 erc20HCIndex, uint64 amountHCDecimals) internal {} + function submitLimitOrder( + uint32 asset, + bool isBuy, + uint64 limitPriceX1e8, + uint64 sizeX1e8, + bool reduceOnly, + uint8 encodedTif, + uint128 cloid + ) internal { + // Basic sanity checks + if (limitPriceX1e8 == 0) revert LimitPxIsZero(); + if (sizeX1e8 == 0) revert OrderSizeIsZero(); + if (!(encodedTif == 1 || encodedTif == 2 || encodedTif == 3)) revert InvalidTif(); + + // Encode the action payload + bytes memory action = abi.encode(asset, isBuy, limitPriceX1e8, sizeX1e8, reduceOnly, encodedTif, cloid); + + // Prefix with the limit-order header + bytes memory payload = abi.encodePacked(LIMIT_ORDER_HEADER, action); + + // Enqueue limit order to HyperCore via CoreWriter precompile + ICoreWriter(CORE_WRITER_PRECOMPILE_ADDRESS).sendRawAction(payload); + } /** - * @notice Enqueue a limit order on HyperCore. + * @notice Enqueue a cancel-order-by-CLOID for a given asset. */ - function enqueueLimitOrder(LimitOrder calldata order) internal {} + function cancelOrderByCloid(uint32 asset, uint128 cloid) internal { + bytes memory body = abi.encode(asset, cloid); + + // Prefix with the cancel-by-cloid header + bytes memory data = abi.encodePacked(CANCEL_BY_CLOID_HEADER, body); + + // Enqueue cancel order by CLOID to HyperCore via CoreWriter precompile + ICoreWriter(CORE_WRITER_PRECOMPILE_ADDRESS).sendRawAction(data); + } /** * @notice Quotes the conversion of evm tokens to hypercore tokens * @param _coreIndexId The core index id of the token to transfer * @param _decimalDiff The decimal difference of evmDecimals - coreDecimals * @param _bridgeAddress The asset bridge address of the token to transfer - * @param _amountLD The number of tokens that the composer received (pre-dusted) that we are trying to send - * @return IHyperAssetAmount - The amount of tokens to send to HyperCore (scaled on evm), dust (to be refunded), and the swap amount (of the tokens scaled on hypercore) + * @param _amountLD The number of tokens that (pre-dusted) that we are trying to send + * @return HyperAssetAmount - The amount of tokens to send to HyperCore (scaled on evm), dust (to be refunded), and the swap amount (of the tokens scaled on hypercore) */ function quoteHyperCoreAmount( uint64 _coreIndexId, @@ -147,9 +173,18 @@ library HyperCoreLib { return _spotBalance.total; } + /** + * @notice Checks if the user exists / has been activated on HyperCore. + */ + function coreUserExists(address user) internal view returns (bool) { + (bool success, bytes memory result) = CORE_USER_EXISTS_PRECOMPILE_ADDRESS.staticcall(abi.encode(user)); + if (!success) revert CoreUserExistsPrecompileCallFailed(); + CoreUserExists memory _coreUserExists = abi.decode(result, (CoreUserExists)); + return _coreUserExists.exists; + } + /** * @notice Converts a core index id to an asset bridge address - * @notice This function is called by the HyperLiquidComposer contract * @param _coreIndexId The core index id to convert * @return _assetBridgeAddress The asset bridge address */ @@ -168,7 +203,6 @@ library HyperCoreLib { /** * @notice Converts an amount and an asset to a evm amount and core amount - * @notice This function is called by the HyperLiquidComposer contract * @param _amount The amount to convert * @param _assetBridgeSupply The maximum amount transferable capped by the number of tokens located on the HyperCore's side of the asset bridge * @param _decimalDiff The decimal difference of evmDecimals - coreDecimals @@ -257,11 +291,4 @@ library HyperCoreLib { amountCore = uint64(amountEVM * scale); } } - - function coreUserExists(address user) internal view returns (bool) { - (bool success, bytes memory result) = CORE_USER_EXISTS_PRECOMPILE_ADDRESS.staticcall(abi.encode(user)); - if (!success) revert CoreUserExistsPrecompileCallFailed(); - CoreUserExists memory _coreUserExists = abi.decode(result, (CoreUserExists)); - return _coreUserExists.exists; - } } From a1937b2399bfbe70de53637e3cc8b5bbf7f41833 Mon Sep 17 00:00:00 2001 From: Taylor Webb Date: Tue, 14 Oct 2025 20:03:48 -0600 Subject: [PATCH 03/16] clean up function & variable naming --- contracts/libraries/HyperCoreLib.sol | 79 ++++++++++++++-------------- 1 file changed, 40 insertions(+), 39 deletions(-) diff --git a/contracts/libraries/HyperCoreLib.sol b/contracts/libraries/HyperCoreLib.sol index 1c8a9f146..b2ca6fe89 100644 --- a/contracts/libraries/HyperCoreLib.sol +++ b/contracts/libraries/HyperCoreLib.sol @@ -5,8 +5,7 @@ import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; // TODO: handle MIT / BUSL license -// Note: -// This library does not check if token recipient is activated on HyperCore +// Note: This library does not check if token recipient is activated on HyperCore interface ICoreWriter { function sendRawAction(bytes calldata data) external; @@ -53,30 +52,31 @@ library HyperCoreLib { error InvalidTif(); /** - * @notice Transfer `amountEVMDecimals` of `erc20` from HyperEVM to `toHCAccount` on HyperCore. + * @notice Transfer `amountEVM` of `erc20` from HyperEVM to `to` on HyperCore. * @dev Returns the amount credited on Core in Core units (post conversion). + * @dev The decimal difference is evmDecimals - coreDecimals */ - function transferERC20ToHyperCore( - address erc20, - uint256 amountEVMDecimals, + function transferERC20ToCore( + address erc20EVMAddress, + uint64 erc20CoreIndex, address to, - uint64 erc20HCIndex, + uint256 amountEVM, int8 decimalDiff - ) internal returns (uint64 coreAmount) { + ) internal returns (uint64 amountCore) { // if the transfer amount exceeds the bridge balance, this wil revert HyperAssetAmount memory amounts = quoteHyperCoreAmount( - erc20HCIndex, + erc20CoreIndex, decimalDiff, - into_assetBridgeAddress(erc20HCIndex), - amountEVMDecimals + into_assetBridgeAddress(erc20CoreIndex), + amountEVM ); if (amounts.evm != 0) { // Transfer the tokens to this contract's address on HyperCore - IERC20(erc20).safeTransfer(into_assetBridgeAddress(erc20HCIndex), amounts.evm); + IERC20(erc20EVMAddress).safeTransfer(into_assetBridgeAddress(erc20CoreIndex), amounts.evm); // Transfer the tokens from this contract on HyperCore to the `to` address on HyperCore - transferERC20OnHyperCore(erc20HCIndex, to, amounts.core); + transferERC20OnCore(erc20CoreIndex, to, amounts.core); return amounts.core; } @@ -89,10 +89,10 @@ library HyperCoreLib { * @notice the `to` address on HyperCore using the CoreWriter precompile * @param erc20CoreIndex The core index of the token * @param to The address to receive tokens on HyperCore - * @param coreAmount The amount to transfer on HyperCore + * @param amountCore The amount to transfer on HyperCore */ - function transferERC20OnHyperCore(uint64 erc20CoreIndex, address to, uint64 coreAmount) internal { - bytes memory action = abi.encode(to, erc20CoreIndex, coreAmount); + function transferERC20OnCore(uint64 erc20CoreIndex, address to, uint64 amountCore) internal { + bytes memory action = abi.encode(to, erc20CoreIndex, amountCore); bytes memory payload = abi.encodePacked(SPOT_SEND_HEADER, action); ICoreWriter(CORE_WRITER_PRECOMPILE_ADDRESS).sendRawAction(payload); @@ -123,24 +123,25 @@ library HyperCoreLib { if (sizeX1e8 == 0) revert OrderSizeIsZero(); if (!(encodedTif == 1 || encodedTif == 2 || encodedTif == 3)) revert InvalidTif(); - // Encode the action payload - bytes memory action = abi.encode(asset, isBuy, limitPriceX1e8, sizeX1e8, reduceOnly, encodedTif, cloid); + // Encode the action + bytes memory encodedAction = abi.encode(asset, isBuy, limitPriceX1e8, sizeX1e8, reduceOnly, encodedTif, cloid); // Prefix with the limit-order header - bytes memory payload = abi.encodePacked(LIMIT_ORDER_HEADER, action); + bytes memory data = abi.encodePacked(LIMIT_ORDER_HEADER, encodedAction); // Enqueue limit order to HyperCore via CoreWriter precompile - ICoreWriter(CORE_WRITER_PRECOMPILE_ADDRESS).sendRawAction(payload); + ICoreWriter(CORE_WRITER_PRECOMPILE_ADDRESS).sendRawAction(data); } /** * @notice Enqueue a cancel-order-by-CLOID for a given asset. */ function cancelOrderByCloid(uint32 asset, uint128 cloid) internal { - bytes memory body = abi.encode(asset, cloid); + // Encode the action + bytes memory encodedAction = abi.encode(asset, cloid); // Prefix with the cancel-by-cloid header - bytes memory data = abi.encodePacked(CANCEL_BY_CLOID_HEADER, body); + bytes memory data = abi.encodePacked(CANCEL_BY_CLOID_HEADER, encodedAction); // Enqueue cancel order by CLOID to HyperCore via CoreWriter precompile ICoreWriter(CORE_WRITER_PRECOMPILE_ADDRESS).sendRawAction(data); @@ -148,19 +149,19 @@ library HyperCoreLib { /** * @notice Quotes the conversion of evm tokens to hypercore tokens - * @param _coreIndexId The core index id of the token to transfer - * @param _decimalDiff The decimal difference of evmDecimals - coreDecimals - * @param _bridgeAddress The asset bridge address of the token to transfer - * @param _amountLD The number of tokens that (pre-dusted) that we are trying to send + * @param erc20CoreIndex The HyperCore index id of the token to transfer + * @param decimalDiff The decimal difference of evmDecimals - coreDecimals + * @param bridgeAddress The asset bridge address of the token to transfer + * @param amountEVM The number of tokens that (pre-dusted) that we are trying to send * @return HyperAssetAmount - The amount of tokens to send to HyperCore (scaled on evm), dust (to be refunded), and the swap amount (of the tokens scaled on hypercore) */ function quoteHyperCoreAmount( - uint64 _coreIndexId, - int8 _decimalDiff, - address _bridgeAddress, - uint256 _amountLD + uint64 erc20CoreIndex, + int8 decimalDiff, + address bridgeAddress, + uint256 amountEVM ) internal view returns (HyperAssetAmount memory) { - return into_hyperAssetAmount(_amountLD, spotBalance(_bridgeAddress, _coreIndexId), _decimalDiff); + return into_hyperAssetAmount(amountEVM, spotBalance(bridgeAddress, erc20CoreIndex), decimalDiff); } /** @@ -185,20 +186,20 @@ library HyperCoreLib { /** * @notice Converts a core index id to an asset bridge address - * @param _coreIndexId The core index id to convert - * @return _assetBridgeAddress The asset bridge address + * @param erc20CoreIndex The core index id to convert + * @return assetBridgeAddress The asset bridge address */ - function into_assetBridgeAddress(uint64 _coreIndexId) internal pure returns (address) { - return address(uint160(BASE_ASSET_BRIDGE_ADDRESS_UINT256 + _coreIndexId)); + function into_assetBridgeAddress(uint64 erc20CoreIndex) internal pure returns (address) { + return address(uint160(BASE_ASSET_BRIDGE_ADDRESS_UINT256 + erc20CoreIndex)); } /** * @notice Converts an asset bridge address to a core index id - * @param _assetBridgeAddress The asset bridge address to convert - * @return _coreIndexId The core index id + * @param assetBridgeAddress The asset bridge address to convert + * @return erc20CoreIndex The core index id */ - function into_tokenId(address _assetBridgeAddress) internal pure returns (uint64) { - return uint64(uint160(_assetBridgeAddress) - BASE_ASSET_BRIDGE_ADDRESS_UINT256); + function into_tokenId(address assetBridgeAddress) internal pure returns (uint64) { + return uint64(uint160(assetBridgeAddress) - BASE_ASSET_BRIDGE_ADDRESS_UINT256); } /** From dbcd4e3ab5ab505e48b319c43a667486e5785da7 Mon Sep 17 00:00:00 2001 From: Taylor Webb Date: Wed, 15 Oct 2025 09:48:20 -0600 Subject: [PATCH 04/16] add tokenInfo getter function --- contracts/libraries/HyperCoreLib.sol | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/contracts/libraries/HyperCoreLib.sol b/contracts/libraries/HyperCoreLib.sol index b2ca6fe89..a090b9e0e 100644 --- a/contracts/libraries/HyperCoreLib.sol +++ b/contracts/libraries/HyperCoreLib.sol @@ -30,10 +30,22 @@ library HyperCoreLib { bool exists; } + struct TokenInfo { + string name; + uint64[] spots; + uint64 deployerTradingFeeShare; + address deployer; + address evmContract; + uint8 szDecimals; + uint8 weiDecimals; + int8 evmExtraWeiDecimals; + } + // Precompile addresses address public constant SPOT_BALANCE_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000000801; address public constant CORE_USER_EXISTS_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000000810; address public constant CORE_WRITER_PRECOMPILE_ADDRESS = 0x3333333333333333333333333333333333333333; + address constant TOKEN_INFO_PRECOMPILE_ADDRESS = 0x000000000000000000000000000000000000080C; // CoreWriter action headers bytes4 public constant LIMIT_ORDER_HEADER = 0x01000001; // version=1, action=1 @@ -47,6 +59,7 @@ library HyperCoreLib { error TransferAmtExceedsAssetBridgeBalance(uint256 amt, uint256 maxAmt); error SpotBalancePrecompileCallFailed(); error CoreUserExistsPrecompileCallFailed(); + error TokenInfoPrecompileCallFailed(); error LimitPxIsZero(); error OrderSizeIsZero(); error InvalidTif(); @@ -184,6 +197,16 @@ library HyperCoreLib { return _coreUserExists.exists; } + /** + * @notice Get the info of the specified token on HyperCore. + */ + function tokenInfo(uint32 token) internal view returns (TokenInfo memory) { + (bool success, bytes memory result) = TOKEN_INFO_PRECOMPILE_ADDRESS.staticcall(abi.encode(token)); + if (!success) revert TokenInfoPrecompileCallFailed(); + TokenInfo memory _tokenInfo = abi.decode(result, (TokenInfo)); + return _tokenInfo; + } + /** * @notice Converts a core index id to an asset bridge address * @param erc20CoreIndex The core index id to convert From c59f0d257a83b490ddbe3bdd9d27c301e1de2114 Mon Sep 17 00:00:00 2001 From: Taylor Webb Date: Wed, 15 Oct 2025 13:10:33 -0600 Subject: [PATCH 05/16] split HyperCoreLib into two libraries --- contracts/libraries/HyperCoreLib.sol | 205 +------------------ contracts/libraries/HyperLiquidHelperLib.sol | 201 ++++++++++++++++++ 2 files changed, 206 insertions(+), 200 deletions(-) create mode 100644 contracts/libraries/HyperLiquidHelperLib.sol diff --git a/contracts/libraries/HyperCoreLib.sol b/contracts/libraries/HyperCoreLib.sol index a090b9e0e..5fd6c9dfd 100644 --- a/contracts/libraries/HyperCoreLib.sol +++ b/contracts/libraries/HyperCoreLib.sol @@ -3,9 +3,7 @@ pragma solidity ^0.8.0; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; - -// TODO: handle MIT / BUSL license -// Note: This library does not check if token recipient is activated on HyperCore +import { HyperLiquidHelperLib } from "./HyperLiquidHelperLib.sol"; interface ICoreWriter { function sendRawAction(bytes calldata data) external; @@ -13,53 +11,16 @@ interface ICoreWriter { library HyperCoreLib { using SafeERC20 for IERC20; - - struct HyperAssetAmount { - uint256 evm; - uint64 core; - uint64 coreBalanceAssetBridge; - } - - struct SpotBalance { - uint64 total; - uint64 hold; // Unused in this implementation - uint64 entryNtl; // Unused in this implementation - } - - struct CoreUserExists { - bool exists; - } - - struct TokenInfo { - string name; - uint64[] spots; - uint64 deployerTradingFeeShare; - address deployer; - address evmContract; - uint8 szDecimals; - uint8 weiDecimals; - int8 evmExtraWeiDecimals; - } + using HyperLiquidHelperLib for *; // Precompile addresses - address public constant SPOT_BALANCE_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000000801; - address public constant CORE_USER_EXISTS_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000000810; address public constant CORE_WRITER_PRECOMPILE_ADDRESS = 0x3333333333333333333333333333333333333333; - address constant TOKEN_INFO_PRECOMPILE_ADDRESS = 0x000000000000000000000000000000000000080C; // CoreWriter action headers bytes4 public constant LIMIT_ORDER_HEADER = 0x01000001; // version=1, action=1 bytes4 public constant SPOT_SEND_HEADER = 0x01000006; // version=1, action=6 bytes4 public constant CANCEL_BY_CLOID_HEADER = 0x0100000B; // version=1, action=11 - // Base asset bridge addresses - address public constant BASE_ASSET_BRIDGE_ADDRESS = 0x2000000000000000000000000000000000000000; - uint256 public constant BASE_ASSET_BRIDGE_ADDRESS_UINT256 = uint256(uint160(BASE_ASSET_BRIDGE_ADDRESS)); - - error TransferAmtExceedsAssetBridgeBalance(uint256 amt, uint256 maxAmt); - error SpotBalancePrecompileCallFailed(); - error CoreUserExistsPrecompileCallFailed(); - error TokenInfoPrecompileCallFailed(); error LimitPxIsZero(); error OrderSizeIsZero(); error InvalidTif(); @@ -77,16 +38,16 @@ library HyperCoreLib { int8 decimalDiff ) internal returns (uint64 amountCore) { // if the transfer amount exceeds the bridge balance, this wil revert - HyperAssetAmount memory amounts = quoteHyperCoreAmount( + HyperLiquidHelperLib.HyperAssetAmount memory amounts = HyperLiquidHelperLib.quoteHyperCoreAmount( erc20CoreIndex, decimalDiff, - into_assetBridgeAddress(erc20CoreIndex), + erc20CoreIndex.into_assetBridgeAddress(), amountEVM ); if (amounts.evm != 0) { // Transfer the tokens to this contract's address on HyperCore - IERC20(erc20EVMAddress).safeTransfer(into_assetBridgeAddress(erc20CoreIndex), amounts.evm); + IERC20(erc20EVMAddress).safeTransfer(erc20CoreIndex.into_assetBridgeAddress(), amounts.evm); // Transfer the tokens from this contract on HyperCore to the `to` address on HyperCore transferERC20OnCore(erc20CoreIndex, to, amounts.core); @@ -159,160 +120,4 @@ library HyperCoreLib { // Enqueue cancel order by CLOID to HyperCore via CoreWriter precompile ICoreWriter(CORE_WRITER_PRECOMPILE_ADDRESS).sendRawAction(data); } - - /** - * @notice Quotes the conversion of evm tokens to hypercore tokens - * @param erc20CoreIndex The HyperCore index id of the token to transfer - * @param decimalDiff The decimal difference of evmDecimals - coreDecimals - * @param bridgeAddress The asset bridge address of the token to transfer - * @param amountEVM The number of tokens that (pre-dusted) that we are trying to send - * @return HyperAssetAmount - The amount of tokens to send to HyperCore (scaled on evm), dust (to be refunded), and the swap amount (of the tokens scaled on hypercore) - */ - function quoteHyperCoreAmount( - uint64 erc20CoreIndex, - int8 decimalDiff, - address bridgeAddress, - uint256 amountEVM - ) internal view returns (HyperAssetAmount memory) { - return into_hyperAssetAmount(amountEVM, spotBalance(bridgeAddress, erc20CoreIndex), decimalDiff); - } - - /** - * @notice Get the balance of the specified ERC20 for `account` on HyperCore. - */ - function spotBalance(address account, uint64 token) internal view returns (uint64 balance) { - (bool success, bytes memory result) = SPOT_BALANCE_PRECOMPILE_ADDRESS.staticcall(abi.encode(account, token)); - if (!success) revert SpotBalancePrecompileCallFailed(); - SpotBalance memory _spotBalance = abi.decode(result, (SpotBalance)); - return _spotBalance.total; - } - - /** - * @notice Checks if the user exists / has been activated on HyperCore. - */ - function coreUserExists(address user) internal view returns (bool) { - (bool success, bytes memory result) = CORE_USER_EXISTS_PRECOMPILE_ADDRESS.staticcall(abi.encode(user)); - if (!success) revert CoreUserExistsPrecompileCallFailed(); - CoreUserExists memory _coreUserExists = abi.decode(result, (CoreUserExists)); - return _coreUserExists.exists; - } - - /** - * @notice Get the info of the specified token on HyperCore. - */ - function tokenInfo(uint32 token) internal view returns (TokenInfo memory) { - (bool success, bytes memory result) = TOKEN_INFO_PRECOMPILE_ADDRESS.staticcall(abi.encode(token)); - if (!success) revert TokenInfoPrecompileCallFailed(); - TokenInfo memory _tokenInfo = abi.decode(result, (TokenInfo)); - return _tokenInfo; - } - - /** - * @notice Converts a core index id to an asset bridge address - * @param erc20CoreIndex The core index id to convert - * @return assetBridgeAddress The asset bridge address - */ - function into_assetBridgeAddress(uint64 erc20CoreIndex) internal pure returns (address) { - return address(uint160(BASE_ASSET_BRIDGE_ADDRESS_UINT256 + erc20CoreIndex)); - } - - /** - * @notice Converts an asset bridge address to a core index id - * @param assetBridgeAddress The asset bridge address to convert - * @return erc20CoreIndex The core index id - */ - function into_tokenId(address assetBridgeAddress) internal pure returns (uint64) { - return uint64(uint160(assetBridgeAddress) - BASE_ASSET_BRIDGE_ADDRESS_UINT256); - } - - /** - * @notice Converts an amount and an asset to a evm amount and core amount - * @param _amount The amount to convert - * @param _assetBridgeSupply The maximum amount transferable capped by the number of tokens located on the HyperCore's side of the asset bridge - * @param _decimalDiff The decimal difference of evmDecimals - coreDecimals - * @return HyperAssetAmount memory - The evm amount and core amount - */ - function into_hyperAssetAmount( - uint256 _amount, - uint64 _assetBridgeSupply, - int8 _decimalDiff - ) internal pure returns (HyperAssetAmount memory) { - uint256 amountEVM; - uint64 amountCore; - - /// @dev HyperLiquid decimal conversion: Scale EVM (u256,evmDecimals) -> Core (u64,coreDecimals) - /// @dev Core amount is guaranteed to be within u64 range. - if (_decimalDiff > 0) { - (amountEVM, amountCore) = into_hyperAssetAmount_decimal_difference_gt_zero( - _amount, - _assetBridgeSupply, - uint8(_decimalDiff) - ); - } else { - (amountEVM, amountCore) = into_hyperAssetAmount_decimal_difference_leq_zero( - _amount, - _assetBridgeSupply, - uint8(-1 * _decimalDiff) - ); - } - - return HyperAssetAmount({ evm: amountEVM, core: amountCore, coreBalanceAssetBridge: _assetBridgeSupply }); - } - - /** - * @notice Computes hyperAssetAmount when EVM decimals > Core decimals - * @notice Reverts if the transfers amount exceeds the asset bridge balance - * @param _amount The amount to convert - * @param _maxTransferableCoreAmount The maximum transferrable amount capped by the asset bridge has range [0,u64.max] - * @param _decimalDiff The decimal difference between HyperEVM and HyperCore - * @return amountEVM The EVM amount - * @return amountCore The core amount - */ - function into_hyperAssetAmount_decimal_difference_gt_zero( - uint256 _amount, - uint64 _maxTransferableCoreAmount, - uint8 _decimalDiff - ) internal pure returns (uint256 amountEVM, uint64 amountCore) { - uint256 scale = 10 ** _decimalDiff; - uint256 maxAmt = _maxTransferableCoreAmount * scale; - - unchecked { - /// @dev Strip out dust from _amount so that _amount and maxEvmAmountFromCoreMax have a maximum of _decimalDiff starting 0s - amountEVM = _amount - (_amount % scale); // Safe: dustAmt = _amount % scale, so dust <= _amount - - if (amountEVM > maxAmt) revert TransferAmtExceedsAssetBridgeBalance(amountEVM, maxAmt); - - /// @dev Safe: Guaranteed to be in the range of [0, u64.max] because it is upperbounded by uint64 maxAmt - amountCore = uint64(amountEVM / scale); - } - } - - /** - * @notice Computes hyperAssetAmount when EVM decimals < Core decimals and 0 - * @notice Reverts if the transfers amount exceeds the asset bridge balance - * @param _amount The amount to convert - * @param _maxTransferableCoreAmount The maximum transferrable amount capped by the asset bridge - * @param _decimalDiff The decimal difference between HyperEVM and HyperCore - * @return amountEVM The EVM amount - * @return amountCore The core amount - */ - function into_hyperAssetAmount_decimal_difference_leq_zero( - uint256 _amount, - uint64 _maxTransferableCoreAmount, - uint8 _decimalDiff - ) internal pure returns (uint256 amountEVM, uint64 amountCore) { - uint256 scale = 10 ** _decimalDiff; - uint256 maxAmt = _maxTransferableCoreAmount / scale; - - unchecked { - amountEVM = _amount; - - /// @dev When `Core > EVM` there will be no opening dust to strip out since all tokens in evm can be represented on core - /// @dev Safe: Bound amountEvm to the range of [0, evmscaled u64.max] - if (_amount > maxAmt) revert TransferAmtExceedsAssetBridgeBalance(amountEVM, maxAmt); - - /// @dev Safe: Guaranteed to be in the range of [0, u64.max] because it is upperbounded by uint64 maxAmt - amountCore = uint64(amountEVM * scale); - } - } } diff --git a/contracts/libraries/HyperLiquidHelperLib.sol b/contracts/libraries/HyperLiquidHelperLib.sol new file mode 100644 index 000000000..95d2f2f96 --- /dev/null +++ b/contracts/libraries/HyperLiquidHelperLib.sol @@ -0,0 +1,201 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +library HyperLiquidHelperLib { + struct HyperAssetAmount { + uint256 evm; + uint64 core; + uint64 coreBalanceAssetBridge; + } + + struct SpotBalance { + uint64 total; + uint64 hold; // Unused in this implementation + uint64 entryNtl; // Unused in this implementation + } + + struct TokenInfo { + string name; + uint64[] spots; + uint64 deployerTradingFeeShare; + address deployer; + address evmContract; + uint8 szDecimals; + uint8 weiDecimals; + int8 evmExtraWeiDecimals; + } + + struct CoreUserExists { + bool exists; + } + + // Base asset bridge addresses + address public constant BASE_ASSET_BRIDGE_ADDRESS = 0x2000000000000000000000000000000000000000; + uint256 public constant BASE_ASSET_BRIDGE_ADDRESS_UINT256 = uint256(uint160(BASE_ASSET_BRIDGE_ADDRESS)); + + // Precompile addresses + address public constant SPOT_BALANCE_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000000801; + address public constant CORE_USER_EXISTS_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000000810; + address constant TOKEN_INFO_PRECOMPILE_ADDRESS = 0x000000000000000000000000000000000000080C; + + error SpotBalancePrecompileCallFailed(); + error CoreUserExistsPrecompileCallFailed(); + error TokenInfoPrecompileCallFailed(); + error TransferAmtExceedsAssetBridgeBalance(uint256 amt, uint256 maxAmt); + + /** + * @notice Get the balance of the specified ERC20 for `account` on HyperCore. + */ + function spotBalance(address account, uint64 token) internal view returns (uint64 balance) { + (bool success, bytes memory result) = SPOT_BALANCE_PRECOMPILE_ADDRESS.staticcall(abi.encode(account, token)); + if (!success) revert SpotBalancePrecompileCallFailed(); + SpotBalance memory _spotBalance = abi.decode(result, (SpotBalance)); + return _spotBalance.total; + } + + /** + * @notice Checks if the user exists / has been activated on HyperCore. + */ + function coreUserExists(address user) internal view returns (bool) { + (bool success, bytes memory result) = CORE_USER_EXISTS_PRECOMPILE_ADDRESS.staticcall(abi.encode(user)); + if (!success) revert CoreUserExistsPrecompileCallFailed(); + CoreUserExists memory _coreUserExists = abi.decode(result, (CoreUserExists)); + return _coreUserExists.exists; + } + + /** + * @notice Get the info of the specified token on HyperCore. + */ + function tokenInfo(uint32 token) internal view returns (TokenInfo memory) { + (bool success, bytes memory result) = TOKEN_INFO_PRECOMPILE_ADDRESS.staticcall(abi.encode(token)); + if (!success) revert TokenInfoPrecompileCallFailed(); + TokenInfo memory _tokenInfo = abi.decode(result, (TokenInfo)); + return _tokenInfo; + } + + /** + * @notice Quotes the conversion of evm tokens to hypercore tokens + * @param erc20CoreIndex The HyperCore index id of the token to transfer + * @param decimalDiff The decimal difference of evmDecimals - coreDecimals + * @param bridgeAddress The asset bridge address of the token to transfer + * @param amountEVM The number of tokens that (pre-dusted) that we are trying to send + * @return HyperAssetAmount - The amount of tokens to send to HyperCore (scaled on evm), dust (to be refunded), and the swap amount (of the tokens scaled on hypercore) + */ + function quoteHyperCoreAmount( + uint64 erc20CoreIndex, + int8 decimalDiff, + address bridgeAddress, + uint256 amountEVM + ) internal view returns (HyperLiquidHelperLib.HyperAssetAmount memory) { + return into_hyperAssetAmount(amountEVM, spotBalance(bridgeAddress, erc20CoreIndex), decimalDiff); + } + + /** + * @notice Converts a core index id to an asset bridge address + * @param erc20CoreIndex The core index id to convert + * @return assetBridgeAddress The asset bridge address + */ + function into_assetBridgeAddress(uint64 erc20CoreIndex) internal pure returns (address) { + return address(uint160(BASE_ASSET_BRIDGE_ADDRESS_UINT256 + erc20CoreIndex)); + } + + /** + * @notice Converts an asset bridge address to a core index id + * @param assetBridgeAddress The asset bridge address to convert + * @return erc20CoreIndex The core index id + */ + function into_tokenId(address assetBridgeAddress) internal pure returns (uint64) { + return uint64(uint160(assetBridgeAddress) - BASE_ASSET_BRIDGE_ADDRESS_UINT256); + } + + /** + * @notice Converts an amount and an asset to a evm amount and core amount + * @param _amount The amount to convert + * @param _assetBridgeSupply The maximum amount transferable capped by the number of tokens located on the HyperCore's side of the asset bridge + * @param _decimalDiff The decimal difference of evmDecimals - coreDecimals + * @return HyperAssetAmount memory - The evm amount and core amount + */ + function into_hyperAssetAmount( + uint256 _amount, + uint64 _assetBridgeSupply, + int8 _decimalDiff + ) internal pure returns (HyperAssetAmount memory) { + uint256 amountEVM; + uint64 amountCore; + + /// @dev HyperLiquid decimal conversion: Scale EVM (u256,evmDecimals) -> Core (u64,coreDecimals) + /// @dev Core amount is guaranteed to be within u64 range. + if (_decimalDiff > 0) { + (amountEVM, amountCore) = into_hyperAssetAmount_decimal_difference_gt_zero( + _amount, + _assetBridgeSupply, + uint8(_decimalDiff) + ); + } else { + (amountEVM, amountCore) = into_hyperAssetAmount_decimal_difference_leq_zero( + _amount, + _assetBridgeSupply, + uint8(-1 * _decimalDiff) + ); + } + + return HyperAssetAmount({ evm: amountEVM, core: amountCore, coreBalanceAssetBridge: _assetBridgeSupply }); + } + + /** + * @notice Computes hyperAssetAmount when EVM decimals > Core decimals + * @notice Reverts if the transfers amount exceeds the asset bridge balance + * @param _amount The amount to convert + * @param _maxTransferableCoreAmount The maximum transferrable amount capped by the asset bridge has range [0,u64.max] + * @param _decimalDiff The decimal difference between HyperEVM and HyperCore + * @return amountEVM The EVM amount + * @return amountCore The core amount + */ + function into_hyperAssetAmount_decimal_difference_gt_zero( + uint256 _amount, + uint64 _maxTransferableCoreAmount, + uint8 _decimalDiff + ) internal pure returns (uint256 amountEVM, uint64 amountCore) { + uint256 scale = 10 ** _decimalDiff; + uint256 maxAmt = _maxTransferableCoreAmount * scale; + + unchecked { + /// @dev Strip out dust from _amount so that _amount and maxEvmAmountFromCoreMax have a maximum of _decimalDiff starting 0s + amountEVM = _amount - (_amount % scale); // Safe: dustAmt = _amount % scale, so dust <= _amount + + if (amountEVM > maxAmt) revert TransferAmtExceedsAssetBridgeBalance(amountEVM, maxAmt); + + /// @dev Safe: Guaranteed to be in the range of [0, u64.max] because it is upperbounded by uint64 maxAmt + amountCore = uint64(amountEVM / scale); + } + } + + /** + * @notice Computes hyperAssetAmount when EVM decimals < Core decimals and 0 + * @notice Reverts if the transfers amount exceeds the asset bridge balance + * @param _amount The amount to convert + * @param _maxTransferableCoreAmount The maximum transferrable amount capped by the asset bridge + * @param _decimalDiff The decimal difference between HyperEVM and HyperCore + * @return amountEVM The EVM amount + * @return amountCore The core amount + */ + function into_hyperAssetAmount_decimal_difference_leq_zero( + uint256 _amount, + uint64 _maxTransferableCoreAmount, + uint8 _decimalDiff + ) internal pure returns (uint256 amountEVM, uint64 amountCore) { + uint256 scale = 10 ** _decimalDiff; + uint256 maxAmt = _maxTransferableCoreAmount / scale; + + unchecked { + amountEVM = _amount; + + /// @dev When `Core > EVM` there will be no opening dust to strip out since all tokens in evm can be represented on core + /// @dev Safe: Bound amountEvm to the range of [0, evmscaled u64.max] + if (_amount > maxAmt) revert TransferAmtExceedsAssetBridgeBalance(amountEVM, maxAmt); + + /// @dev Safe: Guaranteed to be in the range of [0, u64.max] because it is upperbounded by uint64 maxAmt + amountCore = uint64(amountEVM * scale); + } + } +} From 6ef1b9645422413db8f9e98f7de58ac2ef7453af Mon Sep 17 00:00:00 2001 From: Taylor Webb Date: Wed, 15 Oct 2025 13:20:52 -0600 Subject: [PATCH 06/16] rename helper library --- .../{HyperLiquidHelperLib.sol => HyperCoreHelperLib.sol} | 4 ++-- contracts/libraries/HyperCoreLib.sol | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) rename contracts/libraries/{HyperLiquidHelperLib.sol => HyperCoreHelperLib.sol} (98%) diff --git a/contracts/libraries/HyperLiquidHelperLib.sol b/contracts/libraries/HyperCoreHelperLib.sol similarity index 98% rename from contracts/libraries/HyperLiquidHelperLib.sol rename to contracts/libraries/HyperCoreHelperLib.sol index 95d2f2f96..6beb7fa9d 100644 --- a/contracts/libraries/HyperLiquidHelperLib.sol +++ b/contracts/libraries/HyperCoreHelperLib.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -library HyperLiquidHelperLib { +library HyperCoreHelperLib { struct HyperAssetAmount { uint256 evm; uint64 core; @@ -86,7 +86,7 @@ library HyperLiquidHelperLib { int8 decimalDiff, address bridgeAddress, uint256 amountEVM - ) internal view returns (HyperLiquidHelperLib.HyperAssetAmount memory) { + ) internal view returns (HyperAssetAmount memory) { return into_hyperAssetAmount(amountEVM, spotBalance(bridgeAddress, erc20CoreIndex), decimalDiff); } diff --git a/contracts/libraries/HyperCoreLib.sol b/contracts/libraries/HyperCoreLib.sol index 5fd6c9dfd..5a8021bf8 100644 --- a/contracts/libraries/HyperCoreLib.sol +++ b/contracts/libraries/HyperCoreLib.sol @@ -3,7 +3,7 @@ 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 { HyperLiquidHelperLib } from "./HyperLiquidHelperLib.sol"; +import { HyperCoreHelperLib } from "./HyperCoreHelperLib.sol"; interface ICoreWriter { function sendRawAction(bytes calldata data) external; @@ -11,7 +11,7 @@ interface ICoreWriter { library HyperCoreLib { using SafeERC20 for IERC20; - using HyperLiquidHelperLib for *; + using HyperCoreHelperLib for *; // Precompile addresses address public constant CORE_WRITER_PRECOMPILE_ADDRESS = 0x3333333333333333333333333333333333333333; @@ -38,7 +38,7 @@ library HyperCoreLib { int8 decimalDiff ) internal returns (uint64 amountCore) { // if the transfer amount exceeds the bridge balance, this wil revert - HyperLiquidHelperLib.HyperAssetAmount memory amounts = HyperLiquidHelperLib.quoteHyperCoreAmount( + HyperCoreHelperLib.HyperAssetAmount memory amounts = HyperCoreHelperLib.quoteHyperCoreAmount( erc20CoreIndex, decimalDiff, erc20CoreIndex.into_assetBridgeAddress(), From 5465348e8525c55a838b76aee312a68d8964be26 Mon Sep 17 00:00:00 2001 From: Taylor Webb Date: Wed, 15 Oct 2025 13:42:01 -0600 Subject: [PATCH 07/16] add function for bridging to self on Core --- contracts/libraries/HyperCoreLib.sol | 40 ++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/contracts/libraries/HyperCoreLib.sol b/contracts/libraries/HyperCoreLib.sol index 5a8021bf8..d1c1d377e 100644 --- a/contracts/libraries/HyperCoreLib.sol +++ b/contracts/libraries/HyperCoreLib.sol @@ -29,6 +29,12 @@ library HyperCoreLib { * @notice Transfer `amountEVM` of `erc20` from HyperEVM to `to` on HyperCore. * @dev Returns the amount credited on Core in Core units (post conversion). * @dev The decimal difference is evmDecimals - coreDecimals + * @param erc20EVMAddress The address of the ERC20 token on HyperEVM + * @param erc20CoreIndex The HyperCore index id of the token to transfer + * @param to The address to receive tokens on HyperCore + * @param amountEVM The amount to transfer on HyperEVM + * @param decimalDiff The decimal difference of evmDecimals - coreDecimals + * @return amountCore The amount credited on Core in Core units (post conversion) */ function transferERC20ToCore( address erc20EVMAddress, @@ -58,6 +64,40 @@ library HyperCoreLib { return 0; } + /** + * @notice Transfer `amountEVM` of `erc20` from this address on HyperEVM to this address on HyperCore. + * @dev Returns the amount credited on Core in Core units (post conversion). + * @dev The decimal difference is evmDecimals - coreDecimals + * @param erc20EVMAddress The address of the ERC20 token on HyperEVM + * @param erc20CoreIndex The HyperCore index id of the token to transfer + * @param amountEVM The amount to transfer on HyperEVM + * @param decimalDiff The decimal difference of evmDecimals - coreDecimals + * @return amountCore The amount credited on Core in Core units (post conversion) + */ + function transferERC20ToSelfCore( + address erc20EVMAddress, + uint64 erc20CoreIndex, + uint256 amountEVM, + int8 decimalDiff + ) internal returns (uint64 amountCore) { + // if the transfer amount exceeds the bridge balance, this wil revert + HyperCoreHelperLib.HyperAssetAmount memory amounts = HyperCoreHelperLib.quoteHyperCoreAmount( + erc20CoreIndex, + decimalDiff, + erc20CoreIndex.into_assetBridgeAddress(), + amountEVM + ); + + if (amounts.evm != 0) { + // Transfer the tokens to this contract's address on HyperCore + IERC20(erc20EVMAddress).safeTransfer(erc20CoreIndex.into_assetBridgeAddress(), amounts.evm); + + return amounts.core; + } + + return 0; + } + /** * @notice Transfers tokens from this contract on HyperCore to * @notice the `to` address on HyperCore using the CoreWriter precompile From 4217585440ebbce6520ff04509fb145655bd1e9c Mon Sep 17 00:00:00 2001 From: Taylor Webb Date: Wed, 15 Oct 2025 13:59:00 -0600 Subject: [PATCH 08/16] Update natspec and naming --- contracts/libraries/HyperCoreHelperLib.sol | 90 ++++++++++++---------- contracts/libraries/HyperCoreLib.sol | 17 ++-- 2 files changed, 59 insertions(+), 48 deletions(-) diff --git a/contracts/libraries/HyperCoreHelperLib.sol b/contracts/libraries/HyperCoreHelperLib.sol index 6beb7fa9d..d2c9c38e5 100644 --- a/contracts/libraries/HyperCoreHelperLib.sol +++ b/contracts/libraries/HyperCoreHelperLib.sol @@ -45,6 +45,9 @@ library HyperCoreHelperLib { /** * @notice Get the balance of the specified ERC20 for `account` on HyperCore. + * @param account The address of the account to get the balance of + * @param token The token to get the balance of + * @return balance The balance of the specified ERC20 for `account` on HyperCore */ function spotBalance(address account, uint64 token) internal view returns (uint64 balance) { (bool success, bytes memory result) = SPOT_BALANCE_PRECOMPILE_ADDRESS.staticcall(abi.encode(account, token)); @@ -55,6 +58,8 @@ library HyperCoreHelperLib { /** * @notice Checks if the user exists / has been activated on HyperCore. + * @param user The address of the user to check if they exist on HyperCore + * @return exists True if the user exists on HyperCore, false otherwise */ function coreUserExists(address user) internal view returns (bool) { (bool success, bytes memory result) = CORE_USER_EXISTS_PRECOMPILE_ADDRESS.staticcall(abi.encode(user)); @@ -65,9 +70,11 @@ library HyperCoreHelperLib { /** * @notice Get the info of the specified token on HyperCore. + * @param erc20CoreIndex The token to get the info of + * @return tokenInfo The info of the specified token on HyperCore */ - function tokenInfo(uint32 token) internal view returns (TokenInfo memory) { - (bool success, bytes memory result) = TOKEN_INFO_PRECOMPILE_ADDRESS.staticcall(abi.encode(token)); + function tokenInfo(uint32 erc20CoreIndex) internal view returns (TokenInfo memory) { + (bool success, bytes memory result) = TOKEN_INFO_PRECOMPILE_ADDRESS.staticcall(abi.encode(erc20CoreIndex)); if (!success) revert TokenInfoPrecompileCallFailed(); TokenInfo memory _tokenInfo = abi.decode(result, (TokenInfo)); return _tokenInfo; @@ -79,7 +86,8 @@ library HyperCoreHelperLib { * @param decimalDiff The decimal difference of evmDecimals - coreDecimals * @param bridgeAddress The asset bridge address of the token to transfer * @param amountEVM The number of tokens that (pre-dusted) that we are trying to send - * @return HyperAssetAmount - The amount of tokens to send to HyperCore (scaled on evm), dust (to be refunded), and the swap amount (of the tokens scaled on hypercore) + * @return HyperAssetAmount The amount of tokens to send to HyperCore (scaled on evm), + * dust (to be refunded), and the swap amount (of the tokens scaled on hypercore) */ function quoteHyperCoreAmount( uint64 erc20CoreIndex, @@ -92,7 +100,7 @@ library HyperCoreHelperLib { /** * @notice Converts a core index id to an asset bridge address - * @param erc20CoreIndex The core index id to convert + * @param erc20CoreIndex The core token index id to convert * @return assetBridgeAddress The asset bridge address */ function into_assetBridgeAddress(uint64 erc20CoreIndex) internal pure returns (address) { @@ -102,7 +110,7 @@ library HyperCoreHelperLib { /** * @notice Converts an asset bridge address to a core index id * @param assetBridgeAddress The asset bridge address to convert - * @return erc20CoreIndex The core index id + * @return erc20CoreIndex The core token index id */ function into_tokenId(address assetBridgeAddress) internal pure returns (uint64) { return uint64(uint160(assetBridgeAddress) - BASE_ASSET_BRIDGE_ADDRESS_UINT256); @@ -110,60 +118,61 @@ library HyperCoreHelperLib { /** * @notice Converts an amount and an asset to a evm amount and core amount - * @param _amount The amount to convert - * @param _assetBridgeSupply The maximum amount transferable capped by the number of tokens located on the HyperCore's side of the asset bridge - * @param _decimalDiff The decimal difference of evmDecimals - coreDecimals - * @return HyperAssetAmount memory - The evm amount and core amount + * @param amountEVMPreDusted The amount to convert + * @param assetBridgeSupplyCore The maximum amount transferable capped by the number of tokens located on the HyperCore's side of the asset bridge + * @param decimalDiff The decimal difference of evmDecimals - coreDecimals + * @return HyperAssetAmount The evm amount and core amount */ function into_hyperAssetAmount( - uint256 _amount, - uint64 _assetBridgeSupply, - int8 _decimalDiff + uint256 amountEVMPreDusted, + uint64 assetBridgeSupplyCore, + int8 decimalDiff ) internal pure returns (HyperAssetAmount memory) { uint256 amountEVM; uint64 amountCore; /// @dev HyperLiquid decimal conversion: Scale EVM (u256,evmDecimals) -> Core (u64,coreDecimals) /// @dev Core amount is guaranteed to be within u64 range. - if (_decimalDiff > 0) { + if (decimalDiff > 0) { (amountEVM, amountCore) = into_hyperAssetAmount_decimal_difference_gt_zero( - _amount, - _assetBridgeSupply, - uint8(_decimalDiff) + amountEVMPreDusted, + assetBridgeSupplyCore, + uint8(decimalDiff) ); } else { (amountEVM, amountCore) = into_hyperAssetAmount_decimal_difference_leq_zero( - _amount, - _assetBridgeSupply, - uint8(-1 * _decimalDiff) + amountEVMPreDusted, + assetBridgeSupplyCore, + uint8(-1 * decimalDiff) ); } - return HyperAssetAmount({ evm: amountEVM, core: amountCore, coreBalanceAssetBridge: _assetBridgeSupply }); + return HyperAssetAmount({ evm: amountEVM, core: amountCore, coreBalanceAssetBridge: assetBridgeSupplyCore }); } /** * @notice Computes hyperAssetAmount when EVM decimals > Core decimals * @notice Reverts if the transfers amount exceeds the asset bridge balance - * @param _amount The amount to convert - * @param _maxTransferableCoreAmount The maximum transferrable amount capped by the asset bridge has range [0,u64.max] - * @param _decimalDiff The decimal difference between HyperEVM and HyperCore + * @param amountEVMPreDusted The amount to convert + * @param maxTransferableAmountCore The maximum transferrable amount capped by the asset bridge has range [0,u64.max] + * @param decimalDiff The decimal difference between HyperEVM and HyperCore * @return amountEVM The EVM amount * @return amountCore The core amount */ function into_hyperAssetAmount_decimal_difference_gt_zero( - uint256 _amount, - uint64 _maxTransferableCoreAmount, - uint8 _decimalDiff + uint256 amountEVMPreDusted, + uint64 maxTransferableAmountCore, + uint8 decimalDiff ) internal pure returns (uint256 amountEVM, uint64 amountCore) { - uint256 scale = 10 ** _decimalDiff; - uint256 maxAmt = _maxTransferableCoreAmount * scale; + uint256 scale = 10 ** decimalDiff; + uint256 maxTransferableAmountEVM = maxTransferableAmountCore * scale; unchecked { /// @dev Strip out dust from _amount so that _amount and maxEvmAmountFromCoreMax have a maximum of _decimalDiff starting 0s - amountEVM = _amount - (_amount % scale); // Safe: dustAmt = _amount % scale, so dust <= _amount + amountEVM = amountEVMPreDusted - (amountEVMPreDusted % scale); // Safe: dustAmount = amountEVMPreDusted % scale, so dust <= amountEVMPreDusted - if (amountEVM > maxAmt) revert TransferAmtExceedsAssetBridgeBalance(amountEVM, maxAmt); + if (amountEVM > maxTransferableAmountEVM) + revert TransferAmtExceedsAssetBridgeBalance(amountEVM, maxTransferableAmountEVM); /// @dev Safe: Guaranteed to be in the range of [0, u64.max] because it is upperbounded by uint64 maxAmt amountCore = uint64(amountEVM / scale); @@ -173,26 +182,27 @@ library HyperCoreHelperLib { /** * @notice Computes hyperAssetAmount when EVM decimals < Core decimals and 0 * @notice Reverts if the transfers amount exceeds the asset bridge balance - * @param _amount The amount to convert - * @param _maxTransferableCoreAmount The maximum transferrable amount capped by the asset bridge - * @param _decimalDiff The decimal difference between HyperEVM and HyperCore + * @param amountEVMPreDusted The amount to convert + * @param maxTransferableAmountCore The maximum transferrable amount capped by the asset bridge + * @param decimalDiff The decimal difference between HyperEVM and HyperCore * @return amountEVM The EVM amount * @return amountCore The core amount */ function into_hyperAssetAmount_decimal_difference_leq_zero( - uint256 _amount, - uint64 _maxTransferableCoreAmount, - uint8 _decimalDiff + uint256 amountEVMPreDusted, + uint64 maxTransferableAmountCore, + uint8 decimalDiff ) internal pure returns (uint256 amountEVM, uint64 amountCore) { - uint256 scale = 10 ** _decimalDiff; - uint256 maxAmt = _maxTransferableCoreAmount / scale; + uint256 scale = 10 ** decimalDiff; + uint256 maxTransferableAmountEVM = maxTransferableAmountCore / scale; unchecked { - amountEVM = _amount; + amountEVM = amountEVMPreDusted; /// @dev When `Core > EVM` there will be no opening dust to strip out since all tokens in evm can be represented on core /// @dev Safe: Bound amountEvm to the range of [0, evmscaled u64.max] - if (_amount > maxAmt) revert TransferAmtExceedsAssetBridgeBalance(amountEVM, maxAmt); + if (amountEVMPreDusted > maxTransferableAmountEVM) + revert TransferAmtExceedsAssetBridgeBalance(amountEVM, maxTransferableAmountEVM); /// @dev Safe: Guaranteed to be in the range of [0, u64.max] because it is upperbounded by uint64 maxAmt amountCore = uint64(amountEVM * scale); diff --git a/contracts/libraries/HyperCoreLib.sol b/contracts/libraries/HyperCoreLib.sol index d1c1d377e..fc108b363 100644 --- a/contracts/libraries/HyperCoreLib.sol +++ b/contracts/libraries/HyperCoreLib.sol @@ -36,7 +36,7 @@ library HyperCoreLib { * @param decimalDiff The decimal difference of evmDecimals - coreDecimals * @return amountCore The amount credited on Core in Core units (post conversion) */ - function transferERC20ToCore( + function transferERC20EVMToCore( address erc20EVMAddress, uint64 erc20CoreIndex, address to, @@ -56,7 +56,7 @@ library HyperCoreLib { IERC20(erc20EVMAddress).safeTransfer(erc20CoreIndex.into_assetBridgeAddress(), amounts.evm); // Transfer the tokens from this contract on HyperCore to the `to` address on HyperCore - transferERC20OnCore(erc20CoreIndex, to, amounts.core); + transferERC20CoreToCore(erc20CoreIndex, to, amounts.core); return amounts.core; } @@ -65,7 +65,7 @@ library HyperCoreLib { } /** - * @notice Transfer `amountEVM` of `erc20` from this address on HyperEVM to this address on HyperCore. + * @notice Bridges `amountEVM` of `erc20` from this address on HyperEVM to this address on HyperCore. * @dev Returns the amount credited on Core in Core units (post conversion). * @dev The decimal difference is evmDecimals - coreDecimals * @param erc20EVMAddress The address of the ERC20 token on HyperEVM @@ -74,7 +74,7 @@ library HyperCoreLib { * @param decimalDiff The decimal difference of evmDecimals - coreDecimals * @return amountCore The amount credited on Core in Core units (post conversion) */ - function transferERC20ToSelfCore( + function transferERC20EVMToSelfOnCore( address erc20EVMAddress, uint64 erc20CoreIndex, uint256 amountEVM, @@ -99,13 +99,12 @@ library HyperCoreLib { } /** - * @notice Transfers tokens from this contract on HyperCore to - * @notice the `to` address on HyperCore using the CoreWriter precompile - * @param erc20CoreIndex The core index of the token + * @notice Transfers tokens from this contract on HyperCore to the `to` address on HyperCore + * @param erc20CoreIndex The HyperCore index id of the token * @param to The address to receive tokens on HyperCore * @param amountCore The amount to transfer on HyperCore */ - function transferERC20OnCore(uint64 erc20CoreIndex, address to, uint64 amountCore) internal { + function transferERC20CoreToCore(uint64 erc20CoreIndex, address to, uint64 amountCore) internal { bytes memory action = abi.encode(to, erc20CoreIndex, amountCore); bytes memory payload = abi.encodePacked(SPOT_SEND_HEADER, action); @@ -149,6 +148,8 @@ library HyperCoreLib { /** * @notice Enqueue a cancel-order-by-CLOID for a given asset. + * @param asset The asset index of the order + * @param cloid The client order id of the order */ function cancelOrderByCloid(uint32 asset, uint128 cloid) internal { // Encode the action From 4842c49011252dbafe7918a69fb4a4a8372ccc60 Mon Sep 17 00:00:00 2001 From: Taylor Webb Date: Wed, 15 Oct 2025 14:12:02 -0600 Subject: [PATCH 09/16] fix natspec --- contracts/libraries/HyperCoreLib.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/contracts/libraries/HyperCoreLib.sol b/contracts/libraries/HyperCoreLib.sol index fc108b363..ff6e64401 100644 --- a/contracts/libraries/HyperCoreLib.sol +++ b/contracts/libraries/HyperCoreLib.sol @@ -26,9 +26,8 @@ library HyperCoreLib { error InvalidTif(); /** - * @notice Transfer `amountEVM` of `erc20` from HyperEVM to `to` on HyperCore. + * @notice Transfer `amountEVM` from HyperEVM to `to` on HyperCore. * @dev Returns the amount credited on Core in Core units (post conversion). - * @dev The decimal difference is evmDecimals - coreDecimals * @param erc20EVMAddress The address of the ERC20 token on HyperEVM * @param erc20CoreIndex The HyperCore index id of the token to transfer * @param to The address to receive tokens on HyperCore From faf3e0775afa0b64df0ed7bda809881de7d42818 Mon Sep 17 00:00:00 2001 From: Taylor Webb Date: Wed, 15 Oct 2025 15:25:21 -0600 Subject: [PATCH 10/16] combine libraries into single library with MIT license --- contracts/libraries/HyperCoreHelperLib.sol | 211 -------------------- contracts/libraries/HyperCoreLib.sol | 221 ++++++++++++++++++++- 2 files changed, 212 insertions(+), 220 deletions(-) delete mode 100644 contracts/libraries/HyperCoreHelperLib.sol diff --git a/contracts/libraries/HyperCoreHelperLib.sol b/contracts/libraries/HyperCoreHelperLib.sol deleted file mode 100644 index d2c9c38e5..000000000 --- a/contracts/libraries/HyperCoreHelperLib.sol +++ /dev/null @@ -1,211 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -library HyperCoreHelperLib { - struct HyperAssetAmount { - uint256 evm; - uint64 core; - uint64 coreBalanceAssetBridge; - } - - struct SpotBalance { - uint64 total; - uint64 hold; // Unused in this implementation - uint64 entryNtl; // Unused in this implementation - } - - struct TokenInfo { - string name; - uint64[] spots; - uint64 deployerTradingFeeShare; - address deployer; - address evmContract; - uint8 szDecimals; - uint8 weiDecimals; - int8 evmExtraWeiDecimals; - } - - struct CoreUserExists { - bool exists; - } - - // Base asset bridge addresses - address public constant BASE_ASSET_BRIDGE_ADDRESS = 0x2000000000000000000000000000000000000000; - uint256 public constant BASE_ASSET_BRIDGE_ADDRESS_UINT256 = uint256(uint160(BASE_ASSET_BRIDGE_ADDRESS)); - - // Precompile addresses - address public constant SPOT_BALANCE_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000000801; - address public constant CORE_USER_EXISTS_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000000810; - address constant TOKEN_INFO_PRECOMPILE_ADDRESS = 0x000000000000000000000000000000000000080C; - - error SpotBalancePrecompileCallFailed(); - error CoreUserExistsPrecompileCallFailed(); - error TokenInfoPrecompileCallFailed(); - error TransferAmtExceedsAssetBridgeBalance(uint256 amt, uint256 maxAmt); - - /** - * @notice Get the balance of the specified ERC20 for `account` on HyperCore. - * @param account The address of the account to get the balance of - * @param token The token to get the balance of - * @return balance The balance of the specified ERC20 for `account` on HyperCore - */ - function spotBalance(address account, uint64 token) internal view returns (uint64 balance) { - (bool success, bytes memory result) = SPOT_BALANCE_PRECOMPILE_ADDRESS.staticcall(abi.encode(account, token)); - if (!success) revert SpotBalancePrecompileCallFailed(); - SpotBalance memory _spotBalance = abi.decode(result, (SpotBalance)); - return _spotBalance.total; - } - - /** - * @notice Checks if the user exists / has been activated on HyperCore. - * @param user The address of the user to check if they exist on HyperCore - * @return exists True if the user exists on HyperCore, false otherwise - */ - function coreUserExists(address user) internal view returns (bool) { - (bool success, bytes memory result) = CORE_USER_EXISTS_PRECOMPILE_ADDRESS.staticcall(abi.encode(user)); - if (!success) revert CoreUserExistsPrecompileCallFailed(); - CoreUserExists memory _coreUserExists = abi.decode(result, (CoreUserExists)); - return _coreUserExists.exists; - } - - /** - * @notice Get the info of the specified token on HyperCore. - * @param erc20CoreIndex The token to get the info of - * @return tokenInfo The info of the specified token on HyperCore - */ - function tokenInfo(uint32 erc20CoreIndex) internal view returns (TokenInfo memory) { - (bool success, bytes memory result) = TOKEN_INFO_PRECOMPILE_ADDRESS.staticcall(abi.encode(erc20CoreIndex)); - if (!success) revert TokenInfoPrecompileCallFailed(); - TokenInfo memory _tokenInfo = abi.decode(result, (TokenInfo)); - return _tokenInfo; - } - - /** - * @notice Quotes the conversion of evm tokens to hypercore tokens - * @param erc20CoreIndex The HyperCore index id of the token to transfer - * @param decimalDiff The decimal difference of evmDecimals - coreDecimals - * @param bridgeAddress The asset bridge address of the token to transfer - * @param amountEVM The number of tokens that (pre-dusted) that we are trying to send - * @return HyperAssetAmount The amount of tokens to send to HyperCore (scaled on evm), - * dust (to be refunded), and the swap amount (of the tokens scaled on hypercore) - */ - function quoteHyperCoreAmount( - uint64 erc20CoreIndex, - int8 decimalDiff, - address bridgeAddress, - uint256 amountEVM - ) internal view returns (HyperAssetAmount memory) { - return into_hyperAssetAmount(amountEVM, spotBalance(bridgeAddress, erc20CoreIndex), decimalDiff); - } - - /** - * @notice Converts a core index id to an asset bridge address - * @param erc20CoreIndex The core token index id to convert - * @return assetBridgeAddress The asset bridge address - */ - function into_assetBridgeAddress(uint64 erc20CoreIndex) internal pure returns (address) { - return address(uint160(BASE_ASSET_BRIDGE_ADDRESS_UINT256 + erc20CoreIndex)); - } - - /** - * @notice Converts an asset bridge address to a core index id - * @param assetBridgeAddress The asset bridge address to convert - * @return erc20CoreIndex The core token index id - */ - function into_tokenId(address assetBridgeAddress) internal pure returns (uint64) { - return uint64(uint160(assetBridgeAddress) - BASE_ASSET_BRIDGE_ADDRESS_UINT256); - } - - /** - * @notice Converts an amount and an asset to a evm amount and core amount - * @param amountEVMPreDusted The amount to convert - * @param assetBridgeSupplyCore The maximum amount transferable capped by the number of tokens located on the HyperCore's side of the asset bridge - * @param decimalDiff The decimal difference of evmDecimals - coreDecimals - * @return HyperAssetAmount The evm amount and core amount - */ - function into_hyperAssetAmount( - uint256 amountEVMPreDusted, - uint64 assetBridgeSupplyCore, - int8 decimalDiff - ) internal pure returns (HyperAssetAmount memory) { - uint256 amountEVM; - uint64 amountCore; - - /// @dev HyperLiquid decimal conversion: Scale EVM (u256,evmDecimals) -> Core (u64,coreDecimals) - /// @dev Core amount is guaranteed to be within u64 range. - if (decimalDiff > 0) { - (amountEVM, amountCore) = into_hyperAssetAmount_decimal_difference_gt_zero( - amountEVMPreDusted, - assetBridgeSupplyCore, - uint8(decimalDiff) - ); - } else { - (amountEVM, amountCore) = into_hyperAssetAmount_decimal_difference_leq_zero( - amountEVMPreDusted, - assetBridgeSupplyCore, - uint8(-1 * decimalDiff) - ); - } - - return HyperAssetAmount({ evm: amountEVM, core: amountCore, coreBalanceAssetBridge: assetBridgeSupplyCore }); - } - - /** - * @notice Computes hyperAssetAmount when EVM decimals > Core decimals - * @notice Reverts if the transfers amount exceeds the asset bridge balance - * @param amountEVMPreDusted The amount to convert - * @param maxTransferableAmountCore The maximum transferrable amount capped by the asset bridge has range [0,u64.max] - * @param decimalDiff The decimal difference between HyperEVM and HyperCore - * @return amountEVM The EVM amount - * @return amountCore The core amount - */ - function into_hyperAssetAmount_decimal_difference_gt_zero( - uint256 amountEVMPreDusted, - uint64 maxTransferableAmountCore, - uint8 decimalDiff - ) internal pure returns (uint256 amountEVM, uint64 amountCore) { - uint256 scale = 10 ** decimalDiff; - uint256 maxTransferableAmountEVM = maxTransferableAmountCore * scale; - - unchecked { - /// @dev Strip out dust from _amount so that _amount and maxEvmAmountFromCoreMax have a maximum of _decimalDiff starting 0s - amountEVM = amountEVMPreDusted - (amountEVMPreDusted % scale); // Safe: dustAmount = amountEVMPreDusted % scale, so dust <= amountEVMPreDusted - - if (amountEVM > maxTransferableAmountEVM) - revert TransferAmtExceedsAssetBridgeBalance(amountEVM, maxTransferableAmountEVM); - - /// @dev Safe: Guaranteed to be in the range of [0, u64.max] because it is upperbounded by uint64 maxAmt - amountCore = uint64(amountEVM / scale); - } - } - - /** - * @notice Computes hyperAssetAmount when EVM decimals < Core decimals and 0 - * @notice Reverts if the transfers amount exceeds the asset bridge balance - * @param amountEVMPreDusted The amount to convert - * @param maxTransferableAmountCore The maximum transferrable amount capped by the asset bridge - * @param decimalDiff The decimal difference between HyperEVM and HyperCore - * @return amountEVM The EVM amount - * @return amountCore The core amount - */ - function into_hyperAssetAmount_decimal_difference_leq_zero( - uint256 amountEVMPreDusted, - uint64 maxTransferableAmountCore, - uint8 decimalDiff - ) internal pure returns (uint256 amountEVM, uint64 amountCore) { - uint256 scale = 10 ** decimalDiff; - uint256 maxTransferableAmountEVM = maxTransferableAmountCore / scale; - - unchecked { - amountEVM = amountEVMPreDusted; - - /// @dev When `Core > EVM` there will be no opening dust to strip out since all tokens in evm can be represented on core - /// @dev Safe: Bound amountEvm to the range of [0, evmscaled u64.max] - if (amountEVMPreDusted > maxTransferableAmountEVM) - revert TransferAmtExceedsAssetBridgeBalance(amountEVM, maxTransferableAmountEVM); - - /// @dev Safe: Guaranteed to be in the range of [0, u64.max] because it is upperbounded by uint64 maxAmt - amountCore = uint64(amountEVM * scale); - } - } -} diff --git a/contracts/libraries/HyperCoreLib.sol b/contracts/libraries/HyperCoreLib.sol index ff6e64401..0741ba49d 100644 --- a/contracts/libraries/HyperCoreLib.sol +++ b/contracts/libraries/HyperCoreLib.sol @@ -1,9 +1,8 @@ -// SPDX-License-Identifier: BUSL-1.1 +// 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 { HyperCoreHelperLib } from "./HyperCoreHelperLib.sol"; interface ICoreWriter { function sendRawAction(bytes calldata data) external; @@ -11,9 +10,42 @@ interface ICoreWriter { library HyperCoreLib { using SafeERC20 for IERC20; - using HyperCoreHelperLib for *; + + struct HyperAssetAmount { + uint256 evm; + uint64 core; + uint64 coreBalanceAssetBridge; + } + + struct SpotBalance { + uint64 total; + uint64 hold; // Unused in this implementation + uint64 entryNtl; // Unused in this implementation + } + + struct TokenInfo { + string name; + uint64[] spots; + uint64 deployerTradingFeeShare; + address deployer; + address evmContract; + uint8 szDecimals; + uint8 weiDecimals; + int8 evmExtraWeiDecimals; + } + + struct CoreUserExists { + bool exists; + } + + // Base asset bridge addresses + address public constant BASE_ASSET_BRIDGE_ADDRESS = 0x2000000000000000000000000000000000000000; + uint256 public constant BASE_ASSET_BRIDGE_ADDRESS_UINT256 = uint256(uint160(BASE_ASSET_BRIDGE_ADDRESS)); // Precompile addresses + address public constant SPOT_BALANCE_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000000801; + address public constant CORE_USER_EXISTS_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000000810; + address constant TOKEN_INFO_PRECOMPILE_ADDRESS = 0x000000000000000000000000000000000000080C; address public constant CORE_WRITER_PRECOMPILE_ADDRESS = 0x3333333333333333333333333333333333333333; // CoreWriter action headers @@ -21,9 +53,14 @@ library HyperCoreLib { bytes4 public constant SPOT_SEND_HEADER = 0x01000006; // version=1, action=6 bytes4 public constant CANCEL_BY_CLOID_HEADER = 0x0100000B; // version=1, action=11 + // Errors error LimitPxIsZero(); error OrderSizeIsZero(); error InvalidTif(); + error SpotBalancePrecompileCallFailed(); + error CoreUserExistsPrecompileCallFailed(); + error TokenInfoPrecompileCallFailed(); + error TransferAmtExceedsAssetBridgeBalance(uint256 amt, uint256 maxAmt); /** * @notice Transfer `amountEVM` from HyperEVM to `to` on HyperCore. @@ -43,16 +80,16 @@ library HyperCoreLib { int8 decimalDiff ) internal returns (uint64 amountCore) { // if the transfer amount exceeds the bridge balance, this wil revert - HyperCoreHelperLib.HyperAssetAmount memory amounts = HyperCoreHelperLib.quoteHyperCoreAmount( + HyperAssetAmount memory amounts = quoteHyperCoreAmount( erc20CoreIndex, decimalDiff, - erc20CoreIndex.into_assetBridgeAddress(), + into_assetBridgeAddress(erc20CoreIndex), amountEVM ); if (amounts.evm != 0) { // Transfer the tokens to this contract's address on HyperCore - IERC20(erc20EVMAddress).safeTransfer(erc20CoreIndex.into_assetBridgeAddress(), amounts.evm); + IERC20(erc20EVMAddress).safeTransfer(into_assetBridgeAddress(erc20CoreIndex), amounts.evm); // Transfer the tokens from this contract on HyperCore to the `to` address on HyperCore transferERC20CoreToCore(erc20CoreIndex, to, amounts.core); @@ -80,16 +117,16 @@ library HyperCoreLib { int8 decimalDiff ) internal returns (uint64 amountCore) { // if the transfer amount exceeds the bridge balance, this wil revert - HyperCoreHelperLib.HyperAssetAmount memory amounts = HyperCoreHelperLib.quoteHyperCoreAmount( + HyperAssetAmount memory amounts = quoteHyperCoreAmount( erc20CoreIndex, decimalDiff, - erc20CoreIndex.into_assetBridgeAddress(), + into_assetBridgeAddress(erc20CoreIndex), amountEVM ); if (amounts.evm != 0) { // Transfer the tokens to this contract's address on HyperCore - IERC20(erc20EVMAddress).safeTransfer(erc20CoreIndex.into_assetBridgeAddress(), amounts.evm); + IERC20(erc20EVMAddress).safeTransfer(into_assetBridgeAddress(erc20CoreIndex), amounts.evm); return amounts.core; } @@ -160,4 +197,170 @@ library HyperCoreLib { // Enqueue cancel order by CLOID to HyperCore via CoreWriter precompile ICoreWriter(CORE_WRITER_PRECOMPILE_ADDRESS).sendRawAction(data); } + + /** + * @notice Get the balance of the specified ERC20 for `account` on HyperCore. + * @param account The address of the account to get the balance of + * @param token The token to get the balance of + * @return balance The balance of the specified ERC20 for `account` on HyperCore + */ + function spotBalance(address account, uint64 token) internal view returns (uint64 balance) { + (bool success, bytes memory result) = SPOT_BALANCE_PRECOMPILE_ADDRESS.staticcall(abi.encode(account, token)); + if (!success) revert SpotBalancePrecompileCallFailed(); + SpotBalance memory _spotBalance = abi.decode(result, (SpotBalance)); + return _spotBalance.total; + } + + /** + * @notice Checks if the user exists / has been activated on HyperCore. + * @param user The address of the user to check if they exist on HyperCore + * @return exists True if the user exists on HyperCore, false otherwise + */ + function coreUserExists(address user) internal view returns (bool) { + (bool success, bytes memory result) = CORE_USER_EXISTS_PRECOMPILE_ADDRESS.staticcall(abi.encode(user)); + if (!success) revert CoreUserExistsPrecompileCallFailed(); + CoreUserExists memory _coreUserExists = abi.decode(result, (CoreUserExists)); + return _coreUserExists.exists; + } + + /** + * @notice Get the info of the specified token on HyperCore. + * @param erc20CoreIndex The token to get the info of + * @return tokenInfo The info of the specified token on HyperCore + */ + function tokenInfo(uint32 erc20CoreIndex) internal view returns (TokenInfo memory) { + (bool success, bytes memory result) = TOKEN_INFO_PRECOMPILE_ADDRESS.staticcall(abi.encode(erc20CoreIndex)); + if (!success) revert TokenInfoPrecompileCallFailed(); + TokenInfo memory _tokenInfo = abi.decode(result, (TokenInfo)); + return _tokenInfo; + } + + /** + * @notice Quotes the conversion of evm tokens to hypercore tokens + * @param erc20CoreIndex The HyperCore index id of the token to transfer + * @param decimalDiff The decimal difference of evmDecimals - coreDecimals + * @param bridgeAddress The asset bridge address of the token to transfer + * @param amountEVM The number of tokens that (pre-dusted) that we are trying to send + * @return HyperAssetAmount The amount of tokens to send to HyperCore (scaled on evm), + * dust (to be refunded), and the swap amount (of the tokens scaled on hypercore) + */ + function quoteHyperCoreAmount( + uint64 erc20CoreIndex, + int8 decimalDiff, + address bridgeAddress, + uint256 amountEVM + ) internal view returns (HyperAssetAmount memory) { + return into_hyperAssetAmount(amountEVM, spotBalance(bridgeAddress, erc20CoreIndex), decimalDiff); + } + + /** + * @notice Converts a core index id to an asset bridge address + * @param erc20CoreIndex The core token index id to convert + * @return assetBridgeAddress The asset bridge address + */ + function into_assetBridgeAddress(uint64 erc20CoreIndex) internal pure returns (address) { + return address(uint160(BASE_ASSET_BRIDGE_ADDRESS_UINT256 + erc20CoreIndex)); + } + + /** + * @notice Converts an asset bridge address to a core index id + * @param assetBridgeAddress The asset bridge address to convert + * @return erc20CoreIndex The core token index id + */ + function into_tokenId(address assetBridgeAddress) internal pure returns (uint64) { + return uint64(uint160(assetBridgeAddress) - BASE_ASSET_BRIDGE_ADDRESS_UINT256); + } + + /** + * @notice Converts an amount and an asset to a evm amount and core amount + * @param amountEVMPreDusted The amount to convert + * @param assetBridgeSupplyCore The maximum amount transferable capped by the number of tokens located on the HyperCore's side of the asset bridge + * @param decimalDiff The decimal difference of evmDecimals - coreDecimals + * @return HyperAssetAmount The evm amount and core amount + */ + function into_hyperAssetAmount( + uint256 amountEVMPreDusted, + uint64 assetBridgeSupplyCore, + int8 decimalDiff + ) internal pure returns (HyperAssetAmount memory) { + uint256 amountEVM; + uint64 amountCore; + + /// @dev HyperLiquid decimal conversion: Scale EVM (u256,evmDecimals) -> Core (u64,coreDecimals) + /// @dev Core amount is guaranteed to be within u64 range. + if (decimalDiff > 0) { + (amountEVM, amountCore) = into_hyperAssetAmount_decimal_difference_gt_zero( + amountEVMPreDusted, + assetBridgeSupplyCore, + uint8(decimalDiff) + ); + } else { + (amountEVM, amountCore) = into_hyperAssetAmount_decimal_difference_leq_zero( + amountEVMPreDusted, + assetBridgeSupplyCore, + uint8(-1 * decimalDiff) + ); + } + + return HyperAssetAmount({ evm: amountEVM, core: amountCore, coreBalanceAssetBridge: assetBridgeSupplyCore }); + } + + /** + * @notice Computes hyperAssetAmount when EVM decimals > Core decimals + * @notice Reverts if the transfers amount exceeds the asset bridge balance + * @param amountEVMPreDusted The amount to convert + * @param maxTransferableAmountCore The maximum transferrable amount capped by the asset bridge has range [0,u64.max] + * @param decimalDiff The decimal difference between HyperEVM and HyperCore + * @return amountEVM The EVM amount + * @return amountCore The core amount + */ + function into_hyperAssetAmount_decimal_difference_gt_zero( + uint256 amountEVMPreDusted, + uint64 maxTransferableAmountCore, + uint8 decimalDiff + ) internal pure returns (uint256 amountEVM, uint64 amountCore) { + uint256 scale = 10 ** decimalDiff; + uint256 maxTransferableAmountEVM = maxTransferableAmountCore * scale; + + unchecked { + /// @dev Strip out dust from _amount so that _amount and maxEvmAmountFromCoreMax have a maximum of _decimalDiff starting 0s + amountEVM = amountEVMPreDusted - (amountEVMPreDusted % scale); // Safe: dustAmount = amountEVMPreDusted % scale, so dust <= amountEVMPreDusted + + if (amountEVM > maxTransferableAmountEVM) + revert TransferAmtExceedsAssetBridgeBalance(amountEVM, maxTransferableAmountEVM); + + /// @dev Safe: Guaranteed to be in the range of [0, u64.max] because it is upperbounded by uint64 maxAmt + amountCore = uint64(amountEVM / scale); + } + } + + /** + * @notice Computes hyperAssetAmount when EVM decimals < Core decimals and 0 + * @notice Reverts if the transfers amount exceeds the asset bridge balance + * @param amountEVMPreDusted The amount to convert + * @param maxTransferableAmountCore The maximum transferrable amount capped by the asset bridge + * @param decimalDiff The decimal difference between HyperEVM and HyperCore + * @return amountEVM The EVM amount + * @return amountCore The core amount + */ + function into_hyperAssetAmount_decimal_difference_leq_zero( + uint256 amountEVMPreDusted, + uint64 maxTransferableAmountCore, + uint8 decimalDiff + ) internal pure returns (uint256 amountEVM, uint64 amountCore) { + uint256 scale = 10 ** decimalDiff; + uint256 maxTransferableAmountEVM = maxTransferableAmountCore / scale; + + unchecked { + amountEVM = amountEVMPreDusted; + + /// @dev When `Core > EVM` there will be no opening dust to strip out since all tokens in evm can be represented on core + /// @dev Safe: Bound amountEvm to the range of [0, evmscaled u64.max] + if (amountEVMPreDusted > maxTransferableAmountEVM) + revert TransferAmtExceedsAssetBridgeBalance(amountEVM, maxTransferableAmountEVM); + + /// @dev Safe: Guaranteed to be in the range of [0, u64.max] because it is upperbounded by uint64 maxAmt + amountCore = uint64(amountEVM * scale); + } + } } From 4ee9e2dd8adfd7ef5274366f3bbd7ae2655bf845 Mon Sep 17 00:00:00 2001 From: Taylor Webb Date: Wed, 15 Oct 2025 16:17:27 -0600 Subject: [PATCH 11/16] make all function camelCase --- contracts/libraries/HyperCoreLib.sol | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/contracts/libraries/HyperCoreLib.sol b/contracts/libraries/HyperCoreLib.sol index 0741ba49d..9e53a3fd2 100644 --- a/contracts/libraries/HyperCoreLib.sol +++ b/contracts/libraries/HyperCoreLib.sol @@ -83,13 +83,13 @@ library HyperCoreLib { HyperAssetAmount memory amounts = quoteHyperCoreAmount( erc20CoreIndex, decimalDiff, - into_assetBridgeAddress(erc20CoreIndex), + toAssetBridgeAddress(erc20CoreIndex), amountEVM ); if (amounts.evm != 0) { // Transfer the tokens to this contract's address on HyperCore - IERC20(erc20EVMAddress).safeTransfer(into_assetBridgeAddress(erc20CoreIndex), amounts.evm); + IERC20(erc20EVMAddress).safeTransfer(toAssetBridgeAddress(erc20CoreIndex), amounts.evm); // Transfer the tokens from this contract on HyperCore to the `to` address on HyperCore transferERC20CoreToCore(erc20CoreIndex, to, amounts.core); @@ -120,13 +120,13 @@ library HyperCoreLib { HyperAssetAmount memory amounts = quoteHyperCoreAmount( erc20CoreIndex, decimalDiff, - into_assetBridgeAddress(erc20CoreIndex), + toAssetBridgeAddress(erc20CoreIndex), amountEVM ); if (amounts.evm != 0) { // Transfer the tokens to this contract's address on HyperCore - IERC20(erc20EVMAddress).safeTransfer(into_assetBridgeAddress(erc20CoreIndex), amounts.evm); + IERC20(erc20EVMAddress).safeTransfer(toAssetBridgeAddress(erc20CoreIndex), amounts.evm); return amounts.core; } @@ -250,7 +250,7 @@ library HyperCoreLib { address bridgeAddress, uint256 amountEVM ) internal view returns (HyperAssetAmount memory) { - return into_hyperAssetAmount(amountEVM, spotBalance(bridgeAddress, erc20CoreIndex), decimalDiff); + return toHyperAssetAmount(amountEVM, spotBalance(bridgeAddress, erc20CoreIndex), decimalDiff); } /** @@ -258,7 +258,7 @@ library HyperCoreLib { * @param erc20CoreIndex The core token index id to convert * @return assetBridgeAddress The asset bridge address */ - function into_assetBridgeAddress(uint64 erc20CoreIndex) internal pure returns (address) { + function toAssetBridgeAddress(uint64 erc20CoreIndex) internal pure returns (address) { return address(uint160(BASE_ASSET_BRIDGE_ADDRESS_UINT256 + erc20CoreIndex)); } @@ -267,7 +267,7 @@ library HyperCoreLib { * @param assetBridgeAddress The asset bridge address to convert * @return erc20CoreIndex The core token index id */ - function into_tokenId(address assetBridgeAddress) internal pure returns (uint64) { + function toTokenId(address assetBridgeAddress) internal pure returns (uint64) { return uint64(uint160(assetBridgeAddress) - BASE_ASSET_BRIDGE_ADDRESS_UINT256); } @@ -278,7 +278,7 @@ library HyperCoreLib { * @param decimalDiff The decimal difference of evmDecimals - coreDecimals * @return HyperAssetAmount The evm amount and core amount */ - function into_hyperAssetAmount( + function toHyperAssetAmount( uint256 amountEVMPreDusted, uint64 assetBridgeSupplyCore, int8 decimalDiff @@ -289,13 +289,13 @@ library HyperCoreLib { /// @dev HyperLiquid decimal conversion: Scale EVM (u256,evmDecimals) -> Core (u64,coreDecimals) /// @dev Core amount is guaranteed to be within u64 range. if (decimalDiff > 0) { - (amountEVM, amountCore) = into_hyperAssetAmount_decimal_difference_gt_zero( + (amountEVM, amountCore) = toHyperAssetAmountDecimalDifferenceGtZero( amountEVMPreDusted, assetBridgeSupplyCore, uint8(decimalDiff) ); } else { - (amountEVM, amountCore) = into_hyperAssetAmount_decimal_difference_leq_zero( + (amountEVM, amountCore) = toHyperAssetAmountDecimalDifferenceLeqZero( amountEVMPreDusted, assetBridgeSupplyCore, uint8(-1 * decimalDiff) @@ -314,7 +314,7 @@ library HyperCoreLib { * @return amountEVM The EVM amount * @return amountCore The core amount */ - function into_hyperAssetAmount_decimal_difference_gt_zero( + function toHyperAssetAmountDecimalDifferenceGtZero( uint256 amountEVMPreDusted, uint64 maxTransferableAmountCore, uint8 decimalDiff @@ -343,7 +343,7 @@ library HyperCoreLib { * @return amountEVM The EVM amount * @return amountCore The core amount */ - function into_hyperAssetAmount_decimal_difference_leq_zero( + function toHyperAssetAmountDecimalDifferenceLeqZero( uint256 amountEVMPreDusted, uint64 maxTransferableAmountCore, uint8 decimalDiff From a85be6e599844ee6a8b50c229fd20e37f95f0ee8 Mon Sep 17 00:00:00 2001 From: Taylor Webb Date: Wed, 15 Oct 2025 16:52:07 -0600 Subject: [PATCH 12/16] Make tif order types into enum --- contracts/libraries/HyperCoreLib.sol | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/contracts/libraries/HyperCoreLib.sol b/contracts/libraries/HyperCoreLib.sol index 9e53a3fd2..844cf7ce5 100644 --- a/contracts/libraries/HyperCoreLib.sol +++ b/contracts/libraries/HyperCoreLib.sol @@ -11,6 +11,14 @@ interface ICoreWriter { library HyperCoreLib { using SafeERC20 for IERC20; + // Time-in-Force order types + enum Tif { + None, // invalid + ALO, // Add Liquidity Only + GTC, // Good-Till-Cancel + IOC // Immediate-or-Cancel + } + struct HyperAssetAmount { uint256 evm; uint64 core; @@ -155,7 +163,7 @@ library HyperCoreLib { * @param limitPriceX1e8 The limit price of the order scaled by 1e8 * @param sizeX1e8 The size of the order scaled by 1e8 * @param reduceOnly If true, only reduce existing position rather than opening a new opposing order - * @param encodedTif Time-in-Force: 1 = ALO, 2 = GTC, 3 = IOC + * @param tif Time-in-Force: ALO, GTC, IOC (None invalid) * @param cloid The client order id of the order, 0 means no cloid */ function submitLimitOrder( @@ -164,16 +172,16 @@ library HyperCoreLib { uint64 limitPriceX1e8, uint64 sizeX1e8, bool reduceOnly, - uint8 encodedTif, + Tif tif, uint128 cloid ) internal { // Basic sanity checks if (limitPriceX1e8 == 0) revert LimitPxIsZero(); if (sizeX1e8 == 0) revert OrderSizeIsZero(); - if (!(encodedTif == 1 || encodedTif == 2 || encodedTif == 3)) revert InvalidTif(); + if (tif == Tif.None || uint8(tif) > uint8(Tif.IOC)) revert InvalidTif(); // Encode the action - bytes memory encodedAction = abi.encode(asset, isBuy, limitPriceX1e8, sizeX1e8, reduceOnly, encodedTif, cloid); + bytes memory encodedAction = abi.encode(asset, isBuy, limitPriceX1e8, sizeX1e8, reduceOnly, uint8(tif), cloid); // Prefix with the limit-order header bytes memory data = abi.encodePacked(LIMIT_ORDER_HEADER, encodedAction); From bc090777c262a2c1001478f360a039b990dfd8ad Mon Sep 17 00:00:00 2001 From: Taylor Webb Date: Wed, 15 Oct 2025 17:35:30 -0600 Subject: [PATCH 13/16] check tif against tif.max --- contracts/libraries/HyperCoreLib.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/libraries/HyperCoreLib.sol b/contracts/libraries/HyperCoreLib.sol index 844cf7ce5..39f68efbe 100644 --- a/contracts/libraries/HyperCoreLib.sol +++ b/contracts/libraries/HyperCoreLib.sol @@ -178,7 +178,7 @@ library HyperCoreLib { // Basic sanity checks if (limitPriceX1e8 == 0) revert LimitPxIsZero(); if (sizeX1e8 == 0) revert OrderSizeIsZero(); - if (tif == Tif.None || uint8(tif) > uint8(Tif.IOC)) revert InvalidTif(); + if (tif == Tif.None || uint8(tif) > uint8(type(Tif).max)) revert InvalidTif(); // Encode the action bytes memory encodedAction = abi.encode(asset, isBuy, limitPriceX1e8, sizeX1e8, reduceOnly, uint8(tif), cloid); From dba0b4e16cbfc0f1dd5c1a920ef0bbda5f590f51 Mon Sep 17 00:00:00 2001 From: Taylor Webb Date: Thu, 16 Oct 2025 13:25:41 -0600 Subject: [PATCH 14/16] add spotPx function --- contracts/libraries/HyperCoreLib.sol | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/contracts/libraries/HyperCoreLib.sol b/contracts/libraries/HyperCoreLib.sol index 39f68efbe..17ee71521 100644 --- a/contracts/libraries/HyperCoreLib.sol +++ b/contracts/libraries/HyperCoreLib.sol @@ -52,8 +52,9 @@ library HyperCoreLib { // Precompile addresses address public constant SPOT_BALANCE_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000000801; + address public constant SPOT_PX_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000000808; address public constant CORE_USER_EXISTS_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000000810; - address constant TOKEN_INFO_PRECOMPILE_ADDRESS = 0x000000000000000000000000000000000000080C; + address public constant TOKEN_INFO_PRECOMPILE_ADDRESS = 0x000000000000000000000000000000000000080C; address public constant CORE_WRITER_PRECOMPILE_ADDRESS = 0x3333333333333333333333333333333333333333; // CoreWriter action headers @@ -68,6 +69,7 @@ library HyperCoreLib { error SpotBalancePrecompileCallFailed(); error CoreUserExistsPrecompileCallFailed(); error TokenInfoPrecompileCallFailed(); + error SpotPxPrecompileCallFailed(); error TransferAmtExceedsAssetBridgeBalance(uint256 amt, uint256 maxAmt); /** @@ -231,6 +233,17 @@ library HyperCoreLib { return _coreUserExists.exists; } + /** + * @notice Get the spot price of the specified asset on HyperCore. + * @param index The asset index to get the spot price of + * @return spotPx The spot price of the specified asset on HyperCore scaled by 1e8 + */ + function spotPx(uint32 index) external view returns (uint64) { + (bool success, bytes memory result) = SPOT_PX_PRECOMPILE_ADDRESS.staticcall(abi.encode(index)); + if (!success) revert SpotPxPrecompileCallFailed(); + return abi.decode(result, (uint64)); + } + /** * @notice Get the info of the specified token on HyperCore. * @param erc20CoreIndex The token to get the info of From a2298f7cf038aef54d5ff1ba4ecd1e7144dc26c5 Mon Sep 17 00:00:00 2001 From: Taylor Webb Date: Thu, 16 Oct 2025 14:36:48 -0600 Subject: [PATCH 15/16] add minimumCoreAmountsToAmounts --- contracts/libraries/HyperCoreLib.sol | 29 ++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/contracts/libraries/HyperCoreLib.sol b/contracts/libraries/HyperCoreLib.sol index 17ee71521..4c3b9fd50 100644 --- a/contracts/libraries/HyperCoreLib.sol +++ b/contracts/libraries/HyperCoreLib.sol @@ -292,6 +292,35 @@ library HyperCoreLib { return uint64(uint160(assetBridgeAddress) - BASE_ASSET_BRIDGE_ADDRESS_UINT256); } + /** + * @notice Returns an amount to send on HyperEVM to receive AT LEAST the amountCoreDesired on HyperCore + * @param amountCoreDesired The minimum amount desired to receive on HyperCore + * @param decimalDiff The decimal difference of evmDecimals - coreDecimals + * @return amountEVMToSend The amount to send on HyperEVM to receive at least amountCoreDesired on HyperCore + * @return amountCoreToReceive The amount that will be received on core if the amountEVMToSend is sent from HyperEVM + */ + function minimumCoreAmountToAmounts( + uint64 amountCoreDesired, + int8 decimalDiff + ) internal pure returns (uint256 amountEVMToSend, uint64 amountCoreToReceive) { + if (decimalDiff == 0) { + // Same decimals between HyperEVM and HyperCore + amountEVMToSend = uint256(amountCoreDesired); + amountCoreToReceive = amountCoreDesired; + } else if (decimalDiff > 0) { + // EVM token has more decimals than Core + // Scale up to represent the same value in higher-precision EVM units + amountEVMToSend = uint256(amountCoreDesired) * (10 ** uint8(decimalDiff)); + amountCoreToReceive = amountCoreDesired; + } else { + // Core token has more decimals than EVM + // Scale down, rounding UP to avoid shortfall on Core + uint256 scaleDivisor = 10 ** uint8(-decimalDiff); + amountEVMToSend = (uint256(amountCoreDesired) + scaleDivisor - 1) / scaleDivisor; // ceil division + amountCoreToReceive = uint64(amountEVMToSend * scaleDivisor); + } + } + /** * @notice Converts an amount and an asset to a evm amount and core amount * @param amountEVMPreDusted The amount to convert From afcc34a37306b3ce1e8e37042002465b025b39e0 Mon Sep 17 00:00:00 2001 From: Taylor Webb Date: Thu, 16 Oct 2025 15:33:21 -0600 Subject: [PATCH 16/16] clean up decimal conversion functions --- contracts/libraries/HyperCoreLib.sol | 192 +++++++-------------------- 1 file changed, 48 insertions(+), 144 deletions(-) diff --git a/contracts/libraries/HyperCoreLib.sol b/contracts/libraries/HyperCoreLib.sol index 4c3b9fd50..eecbf94b0 100644 --- a/contracts/libraries/HyperCoreLib.sol +++ b/contracts/libraries/HyperCoreLib.sol @@ -19,12 +19,6 @@ library HyperCoreLib { IOC // Immediate-or-Cancel } - struct HyperAssetAmount { - uint256 evm; - uint64 core; - uint64 coreBalanceAssetBridge; - } - struct SpotBalance { uint64 total; uint64 hold; // Unused in this implementation @@ -80,7 +74,8 @@ library HyperCoreLib { * @param to The address to receive tokens on HyperCore * @param amountEVM The amount to transfer on HyperEVM * @param decimalDiff The decimal difference of evmDecimals - coreDecimals - * @return amountCore The amount credited on Core in Core units (post conversion) + * @return amountEVMSent The amount sent on HyperEVM + * @return amountCoreToReceive The amount credited on Core in Core units (post conversion) */ function transferERC20EVMToCore( address erc20EVMAddress, @@ -88,26 +83,19 @@ library HyperCoreLib { address to, uint256 amountEVM, int8 decimalDiff - ) internal returns (uint64 amountCore) { + ) internal returns (uint256 amountEVMSent, uint64 amountCoreToReceive) { // if the transfer amount exceeds the bridge balance, this wil revert - HyperAssetAmount memory amounts = quoteHyperCoreAmount( - erc20CoreIndex, - decimalDiff, - toAssetBridgeAddress(erc20CoreIndex), - amountEVM - ); - - if (amounts.evm != 0) { + (uint256 _amountEVMToSend, uint64 _amountCoreToReceive) = maximumEVMSendAmountToAmounts(amountEVM, decimalDiff); + + if (_amountEVMToSend != 0) { // Transfer the tokens to this contract's address on HyperCore - IERC20(erc20EVMAddress).safeTransfer(toAssetBridgeAddress(erc20CoreIndex), amounts.evm); + IERC20(erc20EVMAddress).safeTransfer(toAssetBridgeAddress(erc20CoreIndex), _amountEVMToSend); // Transfer the tokens from this contract on HyperCore to the `to` address on HyperCore - transferERC20CoreToCore(erc20CoreIndex, to, amounts.core); - - return amounts.core; + transferERC20CoreToCore(erc20CoreIndex, to, _amountCoreToReceive); } - return 0; + return (_amountEVMToSend, _amountCoreToReceive); } /** @@ -118,30 +106,23 @@ library HyperCoreLib { * @param erc20CoreIndex The HyperCore index id of the token to transfer * @param amountEVM The amount to transfer on HyperEVM * @param decimalDiff The decimal difference of evmDecimals - coreDecimals - * @return amountCore The amount credited on Core in Core units (post conversion) + * @return amountEVMSent The amount sent on HyperEVM + * @return amountCoreToReceive The amount credited on Core in Core units (post conversion) */ function transferERC20EVMToSelfOnCore( address erc20EVMAddress, uint64 erc20CoreIndex, uint256 amountEVM, int8 decimalDiff - ) internal returns (uint64 amountCore) { - // if the transfer amount exceeds the bridge balance, this wil revert - HyperAssetAmount memory amounts = quoteHyperCoreAmount( - erc20CoreIndex, - decimalDiff, - toAssetBridgeAddress(erc20CoreIndex), - amountEVM - ); - - if (amounts.evm != 0) { - // Transfer the tokens to this contract's address on HyperCore - IERC20(erc20EVMAddress).safeTransfer(toAssetBridgeAddress(erc20CoreIndex), amounts.evm); + ) internal returns (uint256 amountEVMSent, uint64 amountCoreToReceive) { + (uint256 _amountEVMToSend, uint64 _amountCoreToReceive) = maximumEVMSendAmountToAmounts(amountEVM, decimalDiff); - return amounts.core; + if (_amountEVMToSend != 0) { + // Transfer the tokens to this contract's address on HyperCore + IERC20(erc20EVMAddress).safeTransfer(toAssetBridgeAddress(erc20CoreIndex), _amountEVMToSend); } - return 0; + return (_amountEVMToSend, _amountCoreToReceive); } /** @@ -256,24 +237,6 @@ library HyperCoreLib { return _tokenInfo; } - /** - * @notice Quotes the conversion of evm tokens to hypercore tokens - * @param erc20CoreIndex The HyperCore index id of the token to transfer - * @param decimalDiff The decimal difference of evmDecimals - coreDecimals - * @param bridgeAddress The asset bridge address of the token to transfer - * @param amountEVM The number of tokens that (pre-dusted) that we are trying to send - * @return HyperAssetAmount The amount of tokens to send to HyperCore (scaled on evm), - * dust (to be refunded), and the swap amount (of the tokens scaled on hypercore) - */ - function quoteHyperCoreAmount( - uint64 erc20CoreIndex, - int8 decimalDiff, - address bridgeAddress, - uint256 amountEVM - ) internal view returns (HyperAssetAmount memory) { - return toHyperAssetAmount(amountEVM, spotBalance(bridgeAddress, erc20CoreIndex), decimalDiff); - } - /** * @notice Converts a core index id to an asset bridge address * @param erc20CoreIndex The core token index id to convert @@ -293,124 +256,65 @@ library HyperCoreLib { } /** - * @notice Returns an amount to send on HyperEVM to receive AT LEAST the amountCoreDesired on HyperCore - * @param amountCoreDesired The minimum amount desired to receive on HyperCore + * @notice Returns an amount to send on HyperEVM to receive AT LEAST the minimumCoreReceiveAmount on HyperCore + * @param minimumCoreReceiveAmount The minimum amount desired to receive on HyperCore * @param decimalDiff The decimal difference of evmDecimals - coreDecimals - * @return amountEVMToSend The amount to send on HyperEVM to receive at least amountCoreDesired on HyperCore + * @return amountEVMToSend The amount to send on HyperEVM to receive at least minimumCoreReceiveAmount on HyperCore * @return amountCoreToReceive The amount that will be received on core if the amountEVMToSend is sent from HyperEVM */ - function minimumCoreAmountToAmounts( - uint64 amountCoreDesired, + function minimumCoreReceiveAmountToAmounts( + uint64 minimumCoreReceiveAmount, int8 decimalDiff ) internal pure returns (uint256 amountEVMToSend, uint64 amountCoreToReceive) { if (decimalDiff == 0) { // Same decimals between HyperEVM and HyperCore - amountEVMToSend = uint256(amountCoreDesired); - amountCoreToReceive = amountCoreDesired; + amountEVMToSend = uint256(minimumCoreReceiveAmount); + amountCoreToReceive = minimumCoreReceiveAmount; } else if (decimalDiff > 0) { // EVM token has more decimals than Core // Scale up to represent the same value in higher-precision EVM units - amountEVMToSend = uint256(amountCoreDesired) * (10 ** uint8(decimalDiff)); - amountCoreToReceive = amountCoreDesired; + amountEVMToSend = uint256(minimumCoreReceiveAmount) * (10 ** uint8(decimalDiff)); + amountCoreToReceive = minimumCoreReceiveAmount; } else { // Core token has more decimals than EVM // Scale down, rounding UP to avoid shortfall on Core uint256 scaleDivisor = 10 ** uint8(-decimalDiff); - amountEVMToSend = (uint256(amountCoreDesired) + scaleDivisor - 1) / scaleDivisor; // ceil division + amountEVMToSend = (uint256(minimumCoreReceiveAmount) + scaleDivisor - 1) / scaleDivisor; // ceil division amountCoreToReceive = uint64(amountEVMToSend * scaleDivisor); } } /** - * @notice Converts an amount and an asset to a evm amount and core amount - * @param amountEVMPreDusted The amount to convert - * @param assetBridgeSupplyCore The maximum amount transferable capped by the number of tokens located on the HyperCore's side of the asset bridge + * @notice Converts a maximum EVM amount to send into an EVM amount to send to avoid loss to dust, + * @notice and the corresponding amount that will be recieved on Core. + * @param maximumEVMSendAmount The maximum amount to send on HyperEVM * @param decimalDiff The decimal difference of evmDecimals - coreDecimals - * @return HyperAssetAmount The evm amount and core amount + * @return amountEVMToSend The amount to send on HyperEVM + * @return amountCoreToReceive The amount that will be received on HyperCore if the amountEVMToSend is sent */ - function toHyperAssetAmount( - uint256 amountEVMPreDusted, - uint64 assetBridgeSupplyCore, + function maximumEVMSendAmountToAmounts( + uint256 maximumEVMSendAmount, int8 decimalDiff - ) internal pure returns (HyperAssetAmount memory) { - uint256 amountEVM; - uint64 amountCore; - + ) internal pure returns (uint256 amountEVMToSend, uint64 amountCoreToReceive) { /// @dev HyperLiquid decimal conversion: Scale EVM (u256,evmDecimals) -> Core (u64,coreDecimals) /// @dev Core amount is guaranteed to be within u64 range. - if (decimalDiff > 0) { - (amountEVM, amountCore) = toHyperAssetAmountDecimalDifferenceGtZero( - amountEVMPreDusted, - assetBridgeSupplyCore, - uint8(decimalDiff) - ); - } else { - (amountEVM, amountCore) = toHyperAssetAmountDecimalDifferenceLeqZero( - amountEVMPreDusted, - assetBridgeSupplyCore, - uint8(-1 * decimalDiff) - ); - } - - return HyperAssetAmount({ evm: amountEVM, core: amountCore, coreBalanceAssetBridge: assetBridgeSupplyCore }); - } - - /** - * @notice Computes hyperAssetAmount when EVM decimals > Core decimals - * @notice Reverts if the transfers amount exceeds the asset bridge balance - * @param amountEVMPreDusted The amount to convert - * @param maxTransferableAmountCore The maximum transferrable amount capped by the asset bridge has range [0,u64.max] - * @param decimalDiff The decimal difference between HyperEVM and HyperCore - * @return amountEVM The EVM amount - * @return amountCore The core amount - */ - function toHyperAssetAmountDecimalDifferenceGtZero( - uint256 amountEVMPreDusted, - uint64 maxTransferableAmountCore, - uint8 decimalDiff - ) internal pure returns (uint256 amountEVM, uint64 amountCore) { - uint256 scale = 10 ** decimalDiff; - uint256 maxTransferableAmountEVM = maxTransferableAmountCore * scale; - - unchecked { - /// @dev Strip out dust from _amount so that _amount and maxEvmAmountFromCoreMax have a maximum of _decimalDiff starting 0s - amountEVM = amountEVMPreDusted - (amountEVMPreDusted % scale); // Safe: dustAmount = amountEVMPreDusted % scale, so dust <= amountEVMPreDusted - - if (amountEVM > maxTransferableAmountEVM) - revert TransferAmtExceedsAssetBridgeBalance(amountEVM, maxTransferableAmountEVM); + if (decimalDiff == 0) { + amountEVMToSend = maximumEVMSendAmount; + amountCoreToReceive = uint64(amountEVMToSend); + } else if (decimalDiff > 0) { + // EVM token has more decimals than Core + uint256 scale = 10 ** uint8(decimalDiff); + amountEVMToSend = maximumEVMSendAmount - (maximumEVMSendAmount % scale); // Safe: dustAmount = maximumEVMSendAmount % scale, so dust <= maximumEVMSendAmount /// @dev Safe: Guaranteed to be in the range of [0, u64.max] because it is upperbounded by uint64 maxAmt - amountCore = uint64(amountEVM / scale); - } - } - - /** - * @notice Computes hyperAssetAmount when EVM decimals < Core decimals and 0 - * @notice Reverts if the transfers amount exceeds the asset bridge balance - * @param amountEVMPreDusted The amount to convert - * @param maxTransferableAmountCore The maximum transferrable amount capped by the asset bridge - * @param decimalDiff The decimal difference between HyperEVM and HyperCore - * @return amountEVM The EVM amount - * @return amountCore The core amount - */ - function toHyperAssetAmountDecimalDifferenceLeqZero( - uint256 amountEVMPreDusted, - uint64 maxTransferableAmountCore, - uint8 decimalDiff - ) internal pure returns (uint256 amountEVM, uint64 amountCore) { - uint256 scale = 10 ** decimalDiff; - uint256 maxTransferableAmountEVM = maxTransferableAmountCore / scale; - - unchecked { - amountEVM = amountEVMPreDusted; - - /// @dev When `Core > EVM` there will be no opening dust to strip out since all tokens in evm can be represented on core - /// @dev Safe: Bound amountEvm to the range of [0, evmscaled u64.max] - if (amountEVMPreDusted > maxTransferableAmountEVM) - revert TransferAmtExceedsAssetBridgeBalance(amountEVM, maxTransferableAmountEVM); + amountCoreToReceive = uint64(amountEVMToSend / scale); + } else { + // Core token has more decimals than EVM + uint256 scale = 10 ** uint8(-1 * decimalDiff); + amountEVMToSend = maximumEVMSendAmount; /// @dev Safe: Guaranteed to be in the range of [0, u64.max] because it is upperbounded by uint64 maxAmt - amountCore = uint64(amountEVM * scale); + amountCoreToReceive = uint64(amountEVMToSend * scale); } } }