Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Axelar & wormhole Hooks & ISMs #3069

Draft
wants to merge 27 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
7efcc86
add Wormhole Hook from https://github.com/foxpy/hyperlane-monorepo/tr…
NicholasDotSol Dec 10, 2023
61508a1
Add Axelar post-dispatch hook
NicholasDotSol Dec 13, 2023
2b8a07f
test Axelar Hook quoteDispatch
NicholasDotSol Dec 13, 2023
31d2dd6
remove unused custom error
NicholasDotSol Dec 13, 2023
d00bba9
make _formatPayload view
NicholasDotSol Dec 13, 2023
d20d755
remove apostrophe from error message
NicholasDotSol Dec 13, 2023
36be7bb
add custom metadata formatting library to help with bridge aggregation
NicholasDotSol Dec 14, 2023
eeb04b0
update Axelar hook and tests with new library + small fixes
NicholasDotSol Dec 14, 2023
8f2fd8e
doc fix
NicholasDotSol Dec 14, 2023
2e88766
add _isLatestDispatched check to Axelar hook
NicholasDotSol Dec 14, 2023
8f40524
doc fix
NicholasDotSol Dec 14, 2023
c789755
modify BridgeAggregationHookMetadata to only include Axelar payment data
NicholasDotSol Dec 15, 2023
ceeebfa
generate gmp payload internally
NicholasDotSol Dec 15, 2023
5805e92
hook fixes
NicholasDotSol Dec 16, 2023
cd9056a
add v1 axelar ISM
NicholasDotSol Dec 17, 2023
f4e301d
add v1 wormhole ISM
NicholasDotSol Dec 17, 2023
1932675
add axelar and wormhole to ISM interface
NicholasDotSol Dec 17, 2023
7e163e9
secure hook
NicholasDotSol Dec 18, 2023
93f329d
Merge branch 'Axelar-Hook' into Axelar-Wormhole-ISMs
NicholasDotSol Dec 19, 2023
962d50b
move source and destination values to initializer
NicholasDotSol Dec 26, 2023
6453324
Remove constructor from hooks
NicholasDotSol Dec 28, 2023
53d1401
Remove MailboxClient dependencies
NicholasDotSol Dec 28, 2023
9831a5e
consistency level 202 for full safety
NicholasDotSol Dec 28, 2023
ca018b3
get Axelar payload
NicholasDotSol Jan 28, 2024
2f2c819
remove space
NicholasDotSol Jan 28, 2024
cd56cf6
testing modifications
NicholasDotSol Feb 16, 2024
565801f
change ISM type to null for relayer compatibility
NicholasDotSol Feb 20, 2024
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
188 changes: 188 additions & 0 deletions solidity/contracts/hooks/axelar/AxelarHook.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.8.0;

// ============ Internal Imports ============
import {IPostDispatchHook} from "../../interfaces/hooks/IPostDispatchHook.sol";
import {StandardHookMetadata} from "../libs/StandardHookMetadata.sol";
import {Message} from "../../libs/Message.sol";
import {IMailbox} from "../../interfaces/IMailbox.sol";

// ============ External Imports ============
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {AddressToString} from "../../libs/AddressString.sol";

interface IAxelarGateway {
function callContract(
string calldata destinationChain,
string calldata destinationContractAddress,
bytes calldata payload
) external;
}

interface IAxelarGasService {
function payNativeGasForContractCall(
address sender,
string calldata destinationChain,
string calldata destinationAddress,
bytes calldata payload,
address refundAddress
) external payable;
}
interface IAxelarHookGasService {
function getGas() external view returns (uint256);
}

contract AxelarHookGasService is Ownable {
uint256 public gas;
function setGas(uint256 _newGas) external onlyOwner {
gas = _newGas;
}
function getGas() external view returns (uint256) {
return gas;
}
}

contract AxelarHook is IPostDispatchHook, Ownable {
using StandardHookMetadata for bytes;
using Message for bytes;
using AddressToString for address;

IMailbox public immutable MAILBOX;
IAxelarGasService public immutable AXELAR_GAS_SERVICE;
IAxelarGateway public immutable AXELAR_GATEWAY;
IAxelarHookGasService public immutable AXELAR_HOOK_GAS;
string public DESTINATION_CHAIN;
string public DESTINATION_CONTRACT;

constructor(
address _mailbox,
address axelarGateway,
address axelarGasReceiver,
address axelarHookGasService
) {
MAILBOX = IMailbox(_mailbox);
AXELAR_GATEWAY = IAxelarGateway(axelarGateway);
AXELAR_GAS_SERVICE = IAxelarGasService(axelarGasReceiver);
AXELAR_HOOK_GAS = IAxelarHookGasService(axelarHookGasService);
}

/**
* @notice Initializes the hook with specific targets
*/
function initializeReceiver(
string memory destinationChain,
string memory destionationContract
) external onlyOwner {
// require(
// bytes(DESTINATION_CHAIN).length == 0 &&
// bytes(DESTINATION_CONTRACT).length == 0,
// "Already initialized"
// );
DESTINATION_CHAIN = destinationChain;
DESTINATION_CONTRACT = destionationContract;
}

/**
* @notice Returns an enum that represents the type of hook
*/
function hookType() external pure returns (uint8) {
return uint8(IPostDispatchHook.Types.AXELAR);
}

/**
* @notice Returns whether the hook supports metadata
* @return true the hook supports metadata
*/
function supportsMetadata(bytes calldata) external pure returns (bool) {
return true;
}

function postDispatch(
bytes calldata metadata,
bytes calldata message
) external payable {
// ensure hook only dispatches messages that are dispatched by the mailbox
bytes32 id = message.id();
require(_isLatestDispatched(id), "message not dispatched by mailbox");

bytes memory axelarPayload = _encodeGmpPayload(id);

// Pay for gas used by Axelar with ETH
AXELAR_GAS_SERVICE.payNativeGasForContractCall{value: msg.value}(
address(this),
DESTINATION_CHAIN,
DESTINATION_CONTRACT,
axelarPayload,
metadata.refundAddress(address(0))
);

// bridging call
AXELAR_GATEWAY.callContract(
DESTINATION_CHAIN,
DESTINATION_CONTRACT,
axelarPayload
);
}

/**
* @notice Quote for the amount of value required to run this hook.
*/
function quoteDispatch(
bytes calldata,
bytes calldata
) external view returns (uint256) {
return AXELAR_HOOK_GAS.getGas();
}

/**
* @notice Helper function to encode the Axelar GMP payload
* @param _id The latest id of the current dispatched hyperlane message
* @return bytes The Axelar GMP payload.
*/
function _encodeGmpPayload(bytes32 _id) internal returns (bytes memory) {
// dociding version used by Axelar
bytes4 version = bytes4(uint32(1));

//name of the arguments used in the cross-chain function call
string[] memory argumentNameArray = new string[](3);
argumentNameArray[0] = "origin_address";
argumentNameArray[1] = "origin_chain";
argumentNameArray[2] = "id";
// type of argument used in the cross-chain function call
string[] memory abiTypeArray = new string[](3);
abiTypeArray[0] = "string";
abiTypeArray[1] = "string";
abiTypeArray[2] = "bytes32";

// message argument
bytes memory argValue = abi.encode(
address(this).toString(),
"ethereum-sepolia",
_id
);

// add the function name: (submit_meta) and argument value (_id)
bytes memory gmpPayload = abi.encode(
"submit_meta",
argumentNameArray,
abiTypeArray,
argValue
);

// encode the version and return the payload
return
abi.encodePacked(
version, // version number
gmpPayload
);
}

/**
* @notice Helper function to check wether an ID is the latest dispatched by Mailbox
* @param _id The id to check.
* @return true if latest, false otherwise.
*/
function _isLatestDispatched(bytes32 _id) internal view returns (bool) {
return MAILBOX.latestDispatchedId() == _id;
}
}
59 changes: 59 additions & 0 deletions solidity/contracts/hooks/libs/BridgeAggregationHookMetadata.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.8.0;

/**
* Format of metadata:
*
* [0:32] variant
* [32:] additional metadata
*/
library BridgeAggregationHookMetadata {
struct Metadata {
uint256 AxelarPayment;
}

uint8 private constant AXELAR_PAYMENT_OFFSET = 0;
uint8 private constant MIN_METADATA_LENGTH = 32;

/**
* @notice Returns the required payment for Axelar bridging.
* @param _metadata ABI encoded standard hook metadata.
* @return uint256 Payment amount.
*/
function axelarGasPayment(
bytes calldata _metadata
) internal pure returns (uint256) {
if (_metadata.length < AXELAR_PAYMENT_OFFSET + 32) return 0;
return
uint256(
bytes32(
_metadata[AXELAR_PAYMENT_OFFSET:AXELAR_PAYMENT_OFFSET + 32]
)
);
}

/**
* @notice Returs any additional metadata.
* @param _metadata ABI encoded standard hook metadata.
* @return bytes Additional metadata.
*/
function getBridgeAggregationCustomMetadata(
bytes calldata _metadata
) internal pure returns (bytes calldata) {
if (_metadata.length < MIN_METADATA_LENGTH) return _metadata[0:0];
return _metadata[MIN_METADATA_LENGTH:];
}

/**
* @notice Formats the specified Axelar and Wormhole payments.
* @param _axelarPayment msg.value for the message.
* @param _customMetadata Additional metadata to include.
* @return ABI encoded standard hook metadata.
*/
function formatMetadata(
uint256 _axelarPayment,
bytes memory _customMetadata
) internal pure returns (bytes memory) {
return abi.encodePacked(_axelarPayment, _customMetadata);
}
}
68 changes: 68 additions & 0 deletions solidity/contracts/hooks/wormhole/WormholeHook.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.8.0;

// ============ Internal Imports ============
import {IPostDispatchHook} from "../../interfaces/hooks/IPostDispatchHook.sol";
import {Message} from "../../libs/Message.sol";
import {IMailbox} from "../../interfaces/IMailbox.sol";

// TODO: figure out whether it is possible to import this using Hardhat:
// https://github.com/wormhole-foundation/wormhole/blob/main/ethereum/contracts/interfaces/IWormhole.sol
interface IWormhole {
function publishMessage(
uint32 nonce,
bytes memory payload,
uint8 consistencyLevel
) external payable returns (uint64 sequence);
}

contract WormholeHook is IPostDispatchHook {
using Message for bytes;

IMailbox public immutable MAILBOX;
IWormhole public WORMHOLE;

constructor(address _wormhole, address _mailbox) {
WORMHOLE = IWormhole(_wormhole);
MAILBOX = IMailbox(_mailbox);
}

function hookType() external pure returns (uint8) {
return uint8(IPostDispatchHook.Types.WORMHOLE);
}

function supportsMetadata(bytes calldata) external pure returns (bool) {
return true;
}

function postDispatch(
bytes calldata,
bytes calldata message
) external payable {
// ensure hook only dispatches messages that are dispatched by the mailbox
bytes32 id = message.id();
require(
_isLatestDispatched(id),
"message not dispatched by Hyperlane mailbox"
);
// use 0 nonce, _isLatestDispatched is sufficient check.
// 201 consistency level iis safest as it ensures finality is reached before bridging.
WORMHOLE.publishMessage{value: msg.value}(0, abi.encodePacked(id), 202);
}

function quoteDispatch(
bytes calldata,
bytes calldata
) external pure returns (uint256) {
return 0;
}

/**
* @notice Helper function to check wether an ID is the latest dispatched by Mailbox
* @param _id The id to check.
* @return true if latest, false otherwise.
*/
function _isLatestDispatched(bytes32 _id) internal view returns (bool) {
return MAILBOX.latestDispatchedId() == _id;
}
}
4 changes: 3 additions & 1 deletion solidity/contracts/interfaces/hooks/IPostDispatchHook.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ interface IPostDispatchHook {
FALLBACK_ROUTING,
ID_AUTH_ISM,
PAUSABLE,
PROTOCOL_FEE
PROTOCOL_FEE,
WORMHOLE,
AXELAR
}

/**
Expand Down
Loading
Loading