Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion contracts/external/interfaces/ICoreDepositWallet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ pragma solidity ^0.8.0;
/**
* @title ICoreDepositWallet
* @notice Interface for the core deposit wallet
* @dev Source: https://developers.circle.com/cctp/coredepositwallet-contract-interface#deposit-function
* @dev Source: https://github.com/circlefin/hyperevm-circle-contracts/blob/master/src/interfaces/ICoreDepositWallet.sol
*/
interface ICoreDepositWallet {
/**
Expand All @@ -29,4 +29,13 @@ interface ICoreDepositWallet {
* @param destinationDex The destination dex on HyperCore.
*/
function deposit(uint256 amount, uint32 destinationDex) external;

/**
* @notice Deposit tokens for a recipient
* @param recipient Recipient of the deposit
* @param amount Amount of tokens to deposit
* @param destinationId Forwarding-address-specific id used in conjunction with
* recipient to route the deposit to a specific location.
*/
function depositFor(address recipient, uint256 amount, uint32 destinationId) external;
}
19 changes: 19 additions & 0 deletions contracts/external/libraries/BytesLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,25 @@ library BytesLib {
// https://github.com/GNSPS/solidity-bytes-utils/blob/fc502455bb2a7e26a743378df042612dd50d1eb9/contracts/BytesLib.sol#L323C5-L398C6
// Code was copied, and slightly modified to use revert instead of require

/**
* @notice Reads a uint8 from a bytes array at a given start index
* @param _bytes The bytes array to convert
* @param _start The start index of the uint8
* @return result The uint8 result
*/
function toUint8(bytes memory _bytes, uint256 _start) internal pure returns (uint8) {
if (_bytes.length < _start + 1) {
revert OutOfBounds();
}
uint8 tempUint;

assembly {
tempUint := mload(add(add(_bytes, 0x1), _start))
}

return tempUint;
}

/**
* @notice Reads a uint16 from a bytes array at a given start index
* @param _bytes The bytes array to convert
Expand Down
17 changes: 13 additions & 4 deletions contracts/handlers/HyperliquidDepositHandler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ contract HyperliquidDepositHandler is AcrossMessageHandler, ReentrancyGuard, Own
*/
function sweepCoreFundsToUser(address token, uint64 coreAmount, address user) external onlyOwner nonReentrant {
uint64 tokenIndex = _getTokenInfo(token).tokenId;
HyperCoreLib.transferERC20CoreToCore(tokenIndex, user, coreAmount);
HyperCoreLib.transferERC20SpotToSpot(tokenIndex, user, coreAmount);
}

/**
Expand Down Expand Up @@ -225,12 +225,21 @@ contract HyperliquidDepositHandler is AcrossMessageHandler, ReentrancyGuard, Own
donationBox.withdraw(IERC20(token), amountRequiredToActivate);
// Deposit the activation fee + 1 wei into this contract's core account to pay for the user's
// account activation.
HyperCoreLib.transferERC20EVMToSelfOnCore(token, tokenIndex, amountRequiredToActivate, decimalDiff);
HyperCoreLib.transferERC20CoreToCore(tokenIndex, user, 1);
HyperCoreLib.transferERC20EVMToSelfOnSpot(token, tokenIndex, amountRequiredToActivate, decimalDiff);
HyperCoreLib.transferERC20SpotToSpot(tokenIndex, user, 1);
emit UserAccountActivated(user, token, amountRequiredToActivate);
}

HyperCoreLib.transferERC20EVMToCore(token, tokenIndex, user, evmAmount, decimalDiff);
HyperCoreLib.transferERC20EVMToCore(
token,
tokenIndex,
user,
evmAmount,
decimalDiff,
HyperCoreLib.CORE_SPOT_DEX_ID,
// Account activation is handled in separate CoreWriter actions above
0
);
}

function _verifySignature(address expectedUser, bytes memory signature) internal view returns (bool) {
Expand Down
6 changes: 6 additions & 0 deletions contracts/interfaces/SponsoredCCTPInterface.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ interface SponsoredCCTPInterface {
uint256 maxBpsToSponsor,
uint256 maxUserSlippageBps,
bytes32 finalToken,
uint32 destinationDex,
uint8 accountCreationMode,
bytes signature
);

Expand Down Expand Up @@ -78,6 +80,10 @@ interface SponsoredCCTPInterface {
// The final token that final recipient will receive. This is needed as it can be different from the burnToken
// in which case we perform a swap on the destination chain.
bytes32 finalToken;
// The destination DEX on HyperCore.
uint32 destinationDex;
// AccountCreationMode: Standard or FromUserFunds
uint8 accountCreationMode;
// Execution mode: DirectToCore, ArbitraryActionsToCore, or ArbitraryActionsToEVM
uint8 executionMode;
// Encoded action data for arbitrary execution. Empty for DirectToCore mode.
Expand Down
146 changes: 120 additions & 26 deletions contracts/libraries/HyperCoreLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,10 @@ library HyperCoreLib {
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
bytes4 public constant SEND_ASSET_TO_DEX_HEADER = 0x0100000D; // version=1, action=13

// HyperCore protocol constants
uint32 private constant CORE_SPOT_DEX_ID = type(uint32).max;
uint32 public constant CORE_SPOT_DEX_ID = type(uint32).max;

// Errors
error LimitPxIsZero();
Expand All @@ -81,6 +82,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
* @param destinationDex The destination DEX on HyperCore
* @param accountActivationFeeCore Present if we have to pay for account activation of `to` (meaning we're reducing our send amount)
* @return amountEVMSent The amount sent on HyperEVM
* @return amountCoreToReceive The amount credited on Core in Core units (post conversion)
*/
Expand All @@ -89,18 +92,71 @@ library HyperCoreLib {
uint64 erc20CoreIndex,
address to,
uint256 amountEVM,
int8 decimalDiff
int8 decimalDiff,
uint32 destinationDex,
uint64 accountActivationFeeCore
) internal returns (uint256 amountEVMSent, uint64 amountCoreToReceive) {
// if the transfer amount exceeds the bridge balance, this wil revert
(uint256 _amountEVMToSend, uint64 _amountCoreToReceive) = maximumEVMSendAmountToAmounts(amountEVM, decimalDiff);

if (_amountEVMToSend != 0) {
transferToCore(erc20EVMAddress, erc20CoreIndex, _amountEVMToSend);
// Transfer the tokens from this contract on HyperCore to the `to` address on HyperCore
transferERC20CoreToCore(erc20CoreIndex, to, _amountCoreToReceive);
if (erc20CoreIndex == USDC_CORE_INDEX) {
// USDC flow takes care of account creation fee for us, we don't need to reduce `_amountEVMToSend`
IERC20(erc20EVMAddress).forceApprove(USDC_CORE_DEPOSIT_WALLET_ADDRESS, _amountEVMToSend);
if (to == address(this)) {
ICoreDepositWallet(USDC_CORE_DEPOSIT_WALLET_ADDRESS).deposit(_amountEVMToSend, destinationDex);
} else {
ICoreDepositWallet(USDC_CORE_DEPOSIT_WALLET_ADDRESS).depositFor(
to,
_amountEVMToSend,
destinationDex
);
}
} else {
IERC20(erc20EVMAddress).safeTransfer(toAssetBridgeAddress(erc20CoreIndex), _amountEVMToSend);
// Transfer the tokens from this contract on HyperCore to the `to` address on HyperCore
if (
(to != address(this) || destinationDex != CORE_SPOT_DEX_ID) &&
_amountCoreToReceive > accountActivationFeeCore
) {
transferERC20CoreToCore(
erc20CoreIndex,
to,
_amountCoreToReceive - accountActivationFeeCore,
CORE_SPOT_DEX_ID,
destinationDex
);
}
}
}

return (_amountEVMToSend, _amountCoreToReceive);
return (_amountEVMToSend, _amountCoreToReceive - accountActivationFeeCore);
}

/**
* @notice Bridges `amountEVM` of `erc20` from this address on HyperEVM to this address on HyperCore on the Spot DEX.
* @dev Returns the amount sent on HyperEVM and 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 amountEVMSent The amount sent on HyperEVM
* @return amountCoreToReceive The amount credited on Core in Core units (post conversion)
*/
function transferERC20EVMToSelfOnSpot(
address erc20EVMAddress,
uint64 erc20CoreIndex,
uint256 amountEVM,
int8 decimalDiff
) internal returns (uint256 amountEVMSent, uint64 amountCoreToReceive) {
(amountEVMSent, amountCoreToReceive) = transferERC20EVMToSelfOnCore(
erc20EVMAddress,
erc20CoreIndex,
amountEVM,
decimalDiff,
CORE_SPOT_DEX_ID
);
}

/**
Expand All @@ -111,51 +167,89 @@ 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
* @param destinationDex The destination DEX on HyperCore
* @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
int8 decimalDiff,
uint32 destinationDex
) internal returns (uint256 amountEVMSent, uint64 amountCoreToReceive) {
(uint256 _amountEVMToSend, uint64 _amountCoreToReceive) = maximumEVMSendAmountToAmounts(amountEVM, decimalDiff);

if (_amountEVMToSend != 0) {
transferToCore(erc20EVMAddress, erc20CoreIndex, _amountEVMToSend);
}
return
transferERC20EVMToCore(
erc20EVMAddress,
erc20CoreIndex,
address(this),
amountEVM,
decimalDiff,
destinationDex,
// Contracts that are using this function HAVE to have their accounts created already
0
);
}

return (_amountEVMToSend, _amountCoreToReceive);
/**
* @notice Transfers tokens from this contract on HyperCore to the `to` address on HyperCore on the Spot DEX.
* @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 transferERC20SpotToSpot(uint64 erc20CoreIndex, address to, uint64 amountCore) internal {
transferERC20CoreToCore(erc20CoreIndex, to, amountCore, CORE_SPOT_DEX_ID, CORE_SPOT_DEX_ID);
}

/**
* @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
* @param sourceDex The source DEX on HyperCore
* @param destinationDex The destination DEX on HyperCore
*/
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);
function transferERC20CoreToCore(
uint64 erc20CoreIndex,
address to,
uint64 amountCore,
uint32 sourceDex,
uint32 destinationDex
) internal {
bytes memory action;
bytes memory payload;

if (destinationDex != CORE_SPOT_DEX_ID) {
action = abi.encode(to, address(0), sourceDex, destinationDex, erc20CoreIndex, amountCore);
payload = abi.encodePacked(SEND_ASSET_TO_DEX_HEADER, action);
} else {
action = abi.encode(to, erc20CoreIndex, amountCore);
payload = abi.encodePacked(SPOT_SEND_HEADER, action);
}

ICoreWriter(CORE_WRITER_PRECOMPILE_ADDRESS).sendRawAction(payload);
}

/**
* @notice Transfers tokens from this contract on HyperEVM to this contract's address on HyperCore
* @param erc20EVMAddress The address of the ERC20 token on HyperEVM
* @param erc20CoreIndex The HyperCore index id of the token to transfer
* @param amountEVMToSend The amount to transfer on HyperEVM
* @notice Activate a user account on HyperCore from HyperEVM.
* @param erc20EVMAddress The address of the ERC20 token on HyperEVM.
* @param erc20CoreIndex The HyperCore index id of the token to transfer.
* @param user The address to activate on HyperCore.
* @param amountEVM The amount to transfer on HyperEVM.
*/
function transferToCore(address erc20EVMAddress, uint64 erc20CoreIndex, uint256 amountEVMToSend) internal {
// USDC requires a special transfer to core
function activateCoreAccountFromEVM(
address erc20EVMAddress,
uint64 erc20CoreIndex,
address user,
uint256 amountEVM
) internal {
if (erc20CoreIndex == USDC_CORE_INDEX) {
IERC20(erc20EVMAddress).forceApprove(USDC_CORE_DEPOSIT_WALLET_ADDRESS, amountEVMToSend);
ICoreDepositWallet(USDC_CORE_DEPOSIT_WALLET_ADDRESS).deposit(amountEVMToSend, CORE_SPOT_DEX_ID);
IERC20(erc20EVMAddress).forceApprove(USDC_CORE_DEPOSIT_WALLET_ADDRESS, amountEVM);
ICoreDepositWallet(USDC_CORE_DEPOSIT_WALLET_ADDRESS).depositFor(user, amountEVM, CORE_SPOT_DEX_ID);
} else {
// For all other tokens, transfer to the asset bridge address on HyperCore
IERC20(erc20EVMAddress).safeTransfer(toAssetBridgeAddress(erc20CoreIndex), amountEVMToSend);
IERC20(erc20EVMAddress).safeTransfer(toAssetBridgeAddress(erc20CoreIndex), amountEVM);
// Transfer 1 wei to user on HyperCore to activate account
transferERC20CoreToCore(erc20CoreIndex, user, 1, CORE_SPOT_DEX_ID, CORE_SPOT_DEX_ID);
}
}

Expand Down
14 changes: 10 additions & 4 deletions contracts/libraries/SponsoredCCTPQuoteLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ library SponsoredCCTPQuoteLib {
uint256 private constant HOOK_DATA_INDEX = 228;

// Minimum length of the message body (can be longer due to variable actionData)
uint256 private constant MIN_MSG_BYTES_LENGTH = 664;
uint256 private constant MIN_MSG_BYTES_LENGTH = 728;

/**
* @notice Gets the data for the deposit for burn.
Expand Down Expand Up @@ -82,6 +82,8 @@ library SponsoredCCTPQuoteLib {
quote.maxUserSlippageBps,
quote.finalRecipient,
quote.finalToken,
quote.destinationDex,
quote.accountCreationMode,
quote.executionMode,
quote.actionData
);
Expand Down Expand Up @@ -109,9 +111,9 @@ library SponsoredCCTPQuoteLib {
bytes memory hookData = messageBody.slice(HOOK_DATA_INDEX, messageBody.length);

// Decode to check address validity
(, , , , bytes32 finalRecipient, bytes32 finalToken, , ) = abi.decode(
(, , , , bytes32 finalRecipient, bytes32 finalToken, , , , ) = abi.decode(
hookData,
(bytes32, uint256, uint256, uint256, bytes32, bytes32, uint8, bytes)
(bytes32, uint256, uint256, uint256, bytes32, bytes32, uint32, uint8, uint8, bytes)
);

return finalRecipient.isValidAddress() && finalToken.isValidAddress();
Expand Down Expand Up @@ -147,9 +149,11 @@ library SponsoredCCTPQuoteLib {
quote.maxUserSlippageBps,
quote.finalRecipient,
quote.finalToken,
quote.destinationDex,
quote.accountCreationMode,
quote.executionMode,
quote.actionData
) = abi.decode(hookData, (bytes32, uint256, uint256, uint256, bytes32, bytes32, uint8, bytes));
) = abi.decode(hookData, (bytes32, uint256, uint256, uint256, bytes32, bytes32, uint32, uint8, uint8, bytes));
}

/**
Expand Down Expand Up @@ -186,6 +190,8 @@ library SponsoredCCTPQuoteLib {
quote.maxUserSlippageBps,
quote.finalRecipient,
quote.finalToken,
quote.destinationDex,
quote.accountCreationMode,
quote.executionMode,
keccak256(quote.actionData) // Hash the actionData to keep signature size reasonable
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ abstract contract ArbitraryEVMFlowExecutor {
// This means the swap did happen, so we check the balance of the output token and send it.
finalAmount = finalBalance - finalAmountSnapshot;
} else {
// If we somehow lost final tokens, just set the finalAmount to 0.
// If we somehow lost final tokens(e.g. by depositing into some contract), just set the finalAmount to 0.
finalAmount = 0;
}
}
Expand Down
Loading
Loading