Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
9a0d6af
commit before too late
grasphoper Oct 11, 2025
461b3f9
remove lz deps; reimplement minimal lz options functionality
grasphoper Oct 11, 2025
49dc4ca
some polish and comments
grasphoper Oct 11, 2025
d5d86ab
polish
grasphoper Oct 11, 2025
c1acd48
move things around
grasphoper Oct 11, 2025
8468987
add event for sponsored sends tracking
grasphoper Oct 14, 2025
5a880c2
polish
grasphoper Oct 15, 2025
32ffc29
add barebones DstOFTHandler
grasphoper Oct 15, 2025
0c41282
improve lib quality
grasphoper Oct 15, 2025
cb4e27e
added OFTComposeMsgCodec
grasphoper Oct 15, 2025
2fe9333
progress .. Flow implementaions left
grasphoper Oct 15, 2025
8ec101a
rough first draft of transfer-only flow
grasphoper Oct 16, 2025
a757482
add AccessControl
grasphoper Oct 16, 2025
13ed53a
pull account creation funds from donationbox
grasphoper Oct 16, 2025
9058fad
remove HyperCoreLib
grasphoper Oct 16, 2025
866599c
Merge branch 'audit-oct-20' into oft-periphery
grasphoper Oct 16, 2025
c1cec22
first ROUGHEST draft of swap flow implementation
grasphoper Oct 16, 2025
486b439
a more complete implementation
grasphoper Oct 16, 2025
8fc3aa4
merge audit-oct-20
grasphoper Oct 17, 2025
ac69509
copy Forwarder + misc helper contracts into this branch
grasphoper Oct 17, 2025
2bf4092
adjust DstOFTHandler to use HyperCoreForwarder-implemented flows, ins…
grasphoper Oct 17, 2025
4d8c8a5
update error messageas
grasphoper Oct 17, 2025
828ad73
updates
grasphoper Oct 18, 2025
9070d24
added gas limits and max slippage bps
fusmanii Oct 18, 2025
6c9c5a8
polish
grasphoper Oct 19, 2025
3a6cd39
update HyperCoreForwarder import
grasphoper Oct 19, 2025
bcdfa23
Merge branch 'audit-oct-20' into oft-periphery
grasphoper Oct 19, 2025
4fc0f16
add maxUserSlippageBps to emitted event
grasphoper Oct 19, 2025
af8af0f
polish
grasphoper Oct 19, 2025
dcb2130
fix typechain oddness
grasphoper Oct 19, 2025
db7b47c
update HyperCoreFlowExecutor
grasphoper Oct 19, 2025
987dab9
Deploy script
fusmanii Oct 19, 2025
2c7eab9
idk. fixing typechain :)
grasphoper Oct 19, 2025
64ef13f
deploy + test scripts + fix bugs
grasphoper Oct 20, 2025
d0ada01
update .gitignore and script
grasphoper Oct 20, 2025
906ce36
merge audit branch
grasphoper Oct 20, 2025
7b37bbe
fix
grasphoper Oct 20, 2025
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
25 changes: 25 additions & 0 deletions contracts/external/interfaces/ILayerZeroComposer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// SPDX-License-Identifier: MIT

pragma solidity >=0.8.0;

/**
* @title ILayerZeroComposer
* @dev Copied over from https://github.com/LayerZero-Labs/LayerZero-v2/blob/2ff4988f85b5c94032eb71bbc4073e69c078179d/packages/layerzero-v2/evm/protocol/contracts/interfaces/ILayerZeroComposer.sol#L8
*/
interface ILayerZeroComposer {
/**
* @notice Composes a LayerZero message from an OApp.
* @param _from The address initiating the composition, typically the OApp where the lzReceive was called.
* @param _guid The unique identifier for the corresponding LayerZero src/dst tx.
* @param _message The composed message payload in bytes. NOT necessarily the same payload passed via lzReceive.
* @param _executor The address of the executor for the composed message.
* @param _extraData Additional arbitrary data in bytes passed by the entity who executes the lzCompose.
*/
function lzCompose(
address _from,
bytes32 _guid,
bytes calldata _message,
address _executor,
bytes calldata _extraData
) external payable;
}
12 changes: 12 additions & 0 deletions contracts/interfaces/IOFT.sol
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,15 @@ interface IOFT {
address _refundAddress
) external payable returns (MessagingReceipt memory, OFTReceipt memory);
}

interface IEndpoint {
function eid() external view returns (uint32);
}

interface IOAppCore {
/**
* @notice Retrieves the LayerZero endpoint associated with the OApp.
* @return iEndpoint The LayerZero endpoint as an interface.
*/
function endpoint() external view returns (IEndpoint iEndpoint);
}
15 changes: 15 additions & 0 deletions contracts/libraries/BytesLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,21 @@ library BytesLib {
* FUNCTIONS *
**************************************/

/**
* @notice Reads a uint16 from a bytes array at a given start index
* @param _bytes The bytes array to convert
* @param _start The start index of the uint16
* @return result The uint16 result
*/
function toUint16(bytes memory _bytes, uint256 _start) internal pure returns (uint16 result) {
require(_bytes.length >= _start + 2, "toUint16_outOfBounds");

// solhint-disable-next-line no-inline-assembly
assembly {
result := mload(add(add(_bytes, 0x2), _start))
}
}

/**
* @notice Reads a uint32 from a bytes array at a given start index
* @param _bytes The bytes array to convert
Expand Down
124 changes: 124 additions & 0 deletions contracts/libraries/MinimalLZOptions.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

import { BytesLib } from "./BytesLib.sol";
import "@openzeppelin/contracts/utils/math/SafeCast.sol";

/**
* @title MinimalExecutorOptions
* @notice This library is used to provide minimal required functionality of
* https://github.com/LayerZero-Labs/LayerZero-v2/blob/2ff4988f85b5c94032eb71bbc4073e69c078179d/packages/layerzero-v2/evm/messagelib/contracts/libs/ExecutorOptions.sol#L7
*/
library MinimalExecutorOptions {
uint8 internal constant WORKER_ID = 1;

uint8 internal constant OPTION_TYPE_LZRECEIVE = 1;
uint8 internal constant OPTION_TYPE_LZCOMPOSE = 3;

function encodeLzReceiveOption(uint128 _gas, uint128 _value) internal pure returns (bytes memory) {
return _value == 0 ? abi.encodePacked(_gas) : abi.encodePacked(_gas, _value);
}

function encodeLzComposeOption(uint16 _index, uint128 _gas, uint128 _value) internal pure returns (bytes memory) {
return _value == 0 ? abi.encodePacked(_index, _gas) : abi.encodePacked(_index, _gas, _value);
}
}

/**
* @title MinimalLZOptions
* @notice This library is used to provide minimal functionality of
* https://github.com/LayerZero-Labs/devtools/blob/52ad590ab249f660f803ae3aafcbf7115733359c/packages/oapp-evm/contracts/oapp/libs/OptionsBuilder.sol
*/
library MinimalLZOptions {
// @dev Only used in `onlyType3` modifier
using BytesLib for bytes;
// @dev Only used in `addExecutorOption`
using SafeCast for uint256;

// Constants for options types
uint16 internal constant TYPE_1 = 1; // legacy options type 1
uint16 internal constant TYPE_2 = 2; // legacy options type 2
uint16 internal constant TYPE_3 = 3;

// Custom error message
error InvalidSize(uint256 max, uint256 actual);
error InvalidOptionType(uint16 optionType);

// Modifier to ensure only options of type 3 are used
modifier onlyType3(bytes memory _options) {
if (_options.toUint16(0) != TYPE_3) revert InvalidOptionType(_options.toUint16(0));
_;
}

/**
* @dev Creates a new options container with type 3.
* @return options The newly created options container.
*/
function newOptions() internal pure returns (bytes memory) {
return abi.encodePacked(TYPE_3);
}

/**
* @dev Adds an executor LZ receive option to the existing options.
* @param _options The existing options container.
* @param _gas The gasLimit used on the lzReceive() function in the OApp.
* @param _value The msg.value passed to the lzReceive() function in the OApp.
* @return options The updated options container.
*
* @dev When multiples of this option are added, they are summed by the executor
* eg. if (_gas: 200k, and _value: 1 ether) AND (_gas: 100k, _value: 0.5 ether) are sent in an option to the LayerZeroEndpoint,
* that becomes (300k, 1.5 ether) when the message is executed on the remote lzReceive() function.
*/
function addExecutorLzReceiveOption(
bytes memory _options,
uint128 _gas,
uint128 _value
) internal pure onlyType3(_options) returns (bytes memory) {
bytes memory option = MinimalExecutorOptions.encodeLzReceiveOption(_gas, _value);
return addExecutorOption(_options, MinimalExecutorOptions.OPTION_TYPE_LZRECEIVE, option);
}

/**
* @dev Adds an executor LZ compose option to the existing options.
* @param _options The existing options container.
* @param _index The index for the lzCompose() function call.
* @param _gas The gasLimit for the lzCompose() function call.
* @param _value The msg.value for the lzCompose() function call.
* @return options The updated options container.
*
* @dev When multiples of this option are added, they are summed PER index by the executor on the remote chain.
* @dev If the OApp sends N lzCompose calls on the remote, you must provide N incremented indexes starting with 0.
* ie. When your remote OApp composes (N = 3) messages, you must set this option for index 0,1,2
*/
function addExecutorLzComposeOption(
bytes memory _options,
uint16 _index,
uint128 _gas,
uint128 _value
) internal pure onlyType3(_options) returns (bytes memory) {
bytes memory option = MinimalExecutorOptions.encodeLzComposeOption(_index, _gas, _value);
return addExecutorOption(_options, MinimalExecutorOptions.OPTION_TYPE_LZCOMPOSE, option);
}

/**
* @dev Adds an executor option to the existing options.
* @param _options The existing options container.
* @param _optionType The type of the executor option.
* @param _option The encoded data for the executor option.
* @return options The updated options container.
*/
function addExecutorOption(
bytes memory _options,
uint8 _optionType,
bytes memory _option
) internal pure onlyType3(_options) returns (bytes memory) {
return
abi.encodePacked(
_options,
MinimalExecutorOptions.WORKER_ID,
_option.length.toUint16() + 1, // +1 for optionType
_optionType,
_option
);
}
}
97 changes: 97 additions & 0 deletions contracts/libraries/OFTComposeMsgCodec.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

/**
* @title OFTComposeMsgCodec
* @notice Copied from LZ implementation here:
* https://github.com/LayerZero-Labs/devtools/blob/608915a7e260d995ce28e41c4e4877db9b18613b/packages/oft-evm/contracts/libs/OFTComposeMsgCodec.sol#L5
*
*/
library OFTComposeMsgCodec {
// Offset constants for decoding composed messages
uint8 private constant NONCE_OFFSET = 8;
uint8 private constant SRC_EID_OFFSET = 12;
uint8 private constant AMOUNT_LD_OFFSET = 44;
uint8 private constant COMPOSE_FROM_OFFSET = 76;

/**
* @dev Encodes a OFT composed message.
* @param _nonce The nonce value.
* @param _srcEid The source endpoint ID.
* @param _amountLD The amount in local decimals.
* @param _composeMsg The composed message.
* @return _msg The encoded Composed message.
*/
function encode(
uint64 _nonce,
uint32 _srcEid,
uint256 _amountLD,
bytes memory _composeMsg // 0x[composeFrom][composeMsg]
) internal pure returns (bytes memory _msg) {
_msg = abi.encodePacked(_nonce, _srcEid, _amountLD, _composeMsg);
}

/**
* @dev Retrieves the nonce for the composed message.
* @param _msg The message.
* @return The nonce value.
*/
function nonce(bytes calldata _msg) internal pure returns (uint64) {
return uint64(bytes8(_msg[:NONCE_OFFSET]));
}

/**
* @dev Retrieves the source endpoint ID for the composed message.
* @param _msg The message.
* @return The source endpoint ID.
*/
function srcEid(bytes calldata _msg) internal pure returns (uint32) {
return uint32(bytes4(_msg[NONCE_OFFSET:SRC_EID_OFFSET]));
}

/**
* @dev Retrieves the amount in local decimals from the composed message.
* @param _msg The message.
* @return The amount in local decimals.
*/
function amountLD(bytes calldata _msg) internal pure returns (uint256) {
return uint256(bytes32(_msg[SRC_EID_OFFSET:AMOUNT_LD_OFFSET]));
}

/**
* @dev Retrieves the composeFrom value from the composed message.
* @param _msg The message.
* @return The composeFrom value.
*/
function composeFrom(bytes calldata _msg) internal pure returns (bytes32) {
return bytes32(_msg[AMOUNT_LD_OFFSET:COMPOSE_FROM_OFFSET]);
}

/**
* @dev Retrieves the composed message.
* @param _msg The message.
* @return The composed message.
*/
function composeMsg(bytes calldata _msg) internal pure returns (bytes memory) {
return _msg[COMPOSE_FROM_OFFSET:];
}

/**
* @dev Converts an address to bytes32.
* @param _addr The address to convert.
* @return The bytes32 representation of the address.
*/
function addressToBytes32(address _addr) internal pure returns (bytes32) {
return bytes32(uint256(uint160(_addr)));
}

/**
* @dev Converts bytes32 to an address.
* @param _b The bytes32 value to convert.
* @return The address representation of bytes32.
*/
function bytes32ToAddress(bytes32 _b) internal pure returns (address) {
return address(uint160(uint256(_b)));
}
}
53 changes: 53 additions & 0 deletions contracts/periphery/mintburn/sponsored-oft/ComposeMsgCodec.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.23;
import { BytesLib } from "../../../libraries/BytesLib.sol";

/// @notice Codec for params passed in OFT `composeMsg`.
library ComposeMsgCodec {
uint256 internal constant NONCE_OFFSET = 0;
uint256 internal constant DEADLINE_OFFSET = 32;
uint256 internal constant MAX_BPS_TO_SPONSOR_OFFSET = 64;
uint256 internal constant MAX_USER_SLIPPAGE_BPS_OFFSET = 96;
uint256 internal constant FINAL_RECIPIENT_OFFSET = 128;
uint256 internal constant FINAL_TOKEN_OFFSET = 160;
uint256 internal constant COMPOSE_MSG_BYTE_LENGTH = 192;

function _encode(
bytes32 nonce,
uint256 deadline,
uint256 maxBpsToSponsor,
uint256 maxUserSlippageBps,
bytes32 finalRecipient,
bytes32 finalToken
) internal pure returns (bytes memory) {
return abi.encode(nonce, deadline, maxBpsToSponsor, maxUserSlippageBps, finalRecipient, finalToken);
}

function _getNonce(bytes memory data) internal pure returns (bytes32 v) {
return BytesLib.toBytes32(data, NONCE_OFFSET);
}

function _getDeadline(bytes memory data) internal pure returns (uint256 v) {
return BytesLib.toUint256(data, DEADLINE_OFFSET);
}

function _getMaxBpsToSponsor(bytes memory data) internal pure returns (uint256 v) {
return BytesLib.toUint256(data, MAX_BPS_TO_SPONSOR_OFFSET);
}

function _getMaxUserSlippageBps(bytes memory data) internal pure returns (uint256 v) {
return BytesLib.toUint256(data, MAX_USER_SLIPPAGE_BPS_OFFSET);
}

function _getFinalRecipient(bytes memory data) internal pure returns (bytes32 v) {
return BytesLib.toBytes32(data, FINAL_RECIPIENT_OFFSET);
}

function _getFinalToken(bytes memory data) internal pure returns (bytes32 v) {
return BytesLib.toBytes32(data, FINAL_TOKEN_OFFSET);
}

function _isValidComposeMsgBytelength(bytes memory data) internal pure returns (bool valid) {
valid = data.length == COMPOSE_MSG_BYTE_LENGTH;
}
}
Loading
Loading