-
Notifications
You must be signed in to change notification settings - Fork 23
Wormhole adaptor #94
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
Open
Amxx
wants to merge
17
commits into
master
Choose a base branch
from
interrop/wormhole-adaptor
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Wormhole adaptor #94
Changes from 6 commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
d63e58d
wip
Amxx 70a4daf
Test wormhole adapter
Amxx 6492d92
minimize changes
Amxx bf7e7e2
add quote function
Amxx c628223
cvhange prettier rules
Amxx 69c38a5
Merge branch 'master' into interrop/wormhole-adaptor
Amxx 82027f0
Update remappings.txt
Amxx fceeb40
update wormhole gateway adaptor
Amxx 4b7e226
Merge branch 'master' into interrop/wormhole-adaptor
Amxx 0135754
Update ERC7786Receiver
Amxx f15be8e
Merge branch 'erc7786/receiveMessage-without-attributes' into interro…
Amxx 8244d04
update
Amxx 3a05c3a
Merge branch 'master' into interrop/wormhole-adaptor
Amxx da5762b
Simplify base to align with sending only supporting EVM chains
Amxx 011b755
refactor quote/finalize following ERC7786 pending work
Amxx a6dd68b
up
Amxx cbad532
up
Amxx File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity ^0.8.27; | ||
|
||
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; | ||
import {CAIP10} from "@openzeppelin/contracts/utils/CAIP10.sol"; | ||
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; | ||
import {IWormholeRelayer} from "wormhole-solidity-sdk/interfaces/IWormholeRelayer.sol"; | ||
|
||
abstract contract WormholeGatewayBase is Ownable { | ||
IWormholeRelayer internal immutable _wormholeRelayer; | ||
uint16 internal immutable _wormholeChainId; | ||
|
||
mapping(string caip2 => bytes32 remoteGateway) private _remoteGateways; | ||
mapping(string caip2 => uint24 wormholeId) private _caipToWormholeEquivalence; | ||
mapping(uint16 wormholeId => string caip2) private _wormholeToCaipEquivalence; | ||
|
||
/// @dev A remote gateway has been registered for a chain. | ||
event RegisteredRemoteGateway(string caip2, bytes32 gatewayAddress); | ||
|
||
/// @dev A chain equivalence has been registered. | ||
event RegisteredChainEquivalence(string caip2, uint16 wormholeId); | ||
|
||
/// @dev Error emitted when an unsupported chain is queried. | ||
error UnsupportedChain(string caip2); | ||
error UnsupportedChain2(uint16 wormholeId); | ||
|
||
error ChainEquivalenceAlreadyRegistered(string caip2); | ||
error RemoteGatewayAlreadyRegistered(string caip2); | ||
error UnauthorizedCaller(address); | ||
|
||
modifier onlyWormholeRelayer() { | ||
require(msg.sender == address(_wormholeRelayer), UnauthorizedCaller(msg.sender)); | ||
_; | ||
} | ||
|
||
constructor(IWormholeRelayer wormholeRelayer, uint16 wormholeChainId) { | ||
_wormholeRelayer = wormholeRelayer; | ||
_wormholeChainId = wormholeChainId; | ||
} | ||
|
||
function relayer() public view virtual returns (address) { | ||
return address(_wormholeRelayer); | ||
} | ||
|
||
function supportedChain(string memory caip2) public view virtual returns (bool) { | ||
return _caipToWormholeEquivalence[caip2] & (1 << 16) != 0; | ||
} | ||
|
||
function fromCAIP2(string memory caip2) public view virtual returns (uint16) { | ||
uint24 wormholeId = _caipToWormholeEquivalence[caip2]; | ||
require(wormholeId & (1 << 16) != 0, UnsupportedChain(caip2)); | ||
return uint16(wormholeId); | ||
} | ||
|
||
function toCAIP2(uint16 wormholeId) public view virtual returns (string memory caip2) { | ||
caip2 = _wormholeToCaipEquivalence[wormholeId]; | ||
require(bytes(caip2).length > 0, UnsupportedChain2(wormholeId)); | ||
} | ||
|
||
function getRemoteGateway(string memory caip2) public view virtual returns (bytes32 remoteGateway) { | ||
remoteGateway = _remoteGateways[caip2]; | ||
require(remoteGateway != bytes32(0), UnsupportedChain(caip2)); | ||
} | ||
|
||
function registerChainEquivalence(string calldata caip2, uint16 wormholeId) public virtual onlyOwner { | ||
require(_caipToWormholeEquivalence[caip2] == 0, ChainEquivalenceAlreadyRegistered(caip2)); | ||
_caipToWormholeEquivalence[caip2] = wormholeId | (1 << 16); | ||
_wormholeToCaipEquivalence[wormholeId] = caip2; | ||
emit RegisteredChainEquivalence(caip2, wormholeId); | ||
} | ||
|
||
function registerRemoteGateway(string calldata caip2, bytes32 remoteGateway) public virtual onlyOwner { | ||
require(_remoteGateways[caip2] == bytes32(0), RemoteGatewayAlreadyRegistered(caip2)); | ||
_remoteGateways[caip2] = remoteGateway; | ||
emit RegisteredRemoteGateway(caip2, remoteGateway); | ||
} | ||
} |
58 changes: 58 additions & 0 deletions
58
contracts/crosschain/wormhole/WormholeGatewayDestination.sol
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity ^0.8.27; | ||
|
||
import {IWormholeReceiver} from "wormhole-solidity-sdk/interfaces/IWormholeReceiver.sol"; | ||
import {BitMaps} from "@openzeppelin/contracts/utils/structs/BitMaps.sol"; | ||
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; | ||
import {IERC7786Receiver} from "../../interfaces/IERC7786.sol"; | ||
import {WormholeGatewayBase} from "./WormholeGatewayBase.sol"; | ||
|
||
abstract contract WormholeGatewayDestination is WormholeGatewayBase, IWormholeReceiver { | ||
using BitMaps for BitMaps.BitMap; | ||
using Strings for *; | ||
|
||
BitMaps.BitMap private _executed; | ||
|
||
error InvalidOriginGateway(string sourceChain, bytes32 wormholeSourceAddress); | ||
error MessageAlreadyExecuted(bytes32 outboxId); | ||
error ReceiverExecutionFailed(); | ||
error AdditionalMessagesNotSupported(); | ||
|
||
function receiveWormholeMessages( | ||
bytes memory adapterPayload, | ||
bytes[] memory additionalMessages, | ||
bytes32 wormholeSourceAddress, | ||
uint16 wormholeSourceChain, | ||
bytes32 deliveryHash | ||
) public payable virtual onlyWormholeRelayer { | ||
string memory sourceChain = toCAIP2(wormholeSourceChain); | ||
|
||
require(additionalMessages.length == 0, AdditionalMessagesNotSupported()); | ||
require( | ||
getRemoteGateway(sourceChain) == wormholeSourceAddress, | ||
InvalidOriginGateway(sourceChain, wormholeSourceAddress) | ||
); | ||
|
||
( | ||
bytes32 outboxId, | ||
string memory sender, | ||
string memory receiver, | ||
bytes memory payload, | ||
bytes[] memory attributes | ||
) = abi.decode(adapterPayload, (bytes32, string, string, bytes, bytes[])); | ||
|
||
// prevent replay - deliveryHash might not be unique if a message is relayed multiple time | ||
require(!_executed.get(uint256(outboxId)), MessageAlreadyExecuted(outboxId)); | ||
_executed.set(uint256(outboxId)); | ||
|
||
bytes4 result = IERC7786Receiver(receiver.parseAddress()).executeMessage( | ||
uint256(deliveryHash).toHexString(32), | ||
sourceChain, | ||
sender, | ||
payload, | ||
attributes | ||
); | ||
require(result == IERC7786Receiver.executeMessage.selector, ReceiverExecutionFailed()); | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity ^0.8.27; | ||
|
||
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; | ||
import {WormholeGatewayBase, IWormholeRelayer} from "./WormholeGatewayBase.sol"; | ||
import {WormholeGatewayDestination} from "./WormholeGatewayDestination.sol"; | ||
import {WormholeGatewaySource} from "./WormholeGatewaySource.sol"; | ||
|
||
/** | ||
* @dev A contract that combines the functionality of both the source and destination gateway | ||
* adapters for the Wormhole Network. Allowing to either send or receive messages across chains. | ||
*/ | ||
// slither-disable-next-line locked-ether | ||
contract WormholeGatewayDuplex is WormholeGatewaySource, WormholeGatewayDestination { | ||
/// @dev Initializes the contract with the Wormhole gateway and the initial owner. | ||
constructor( | ||
IWormholeRelayer wormholeRelayer, | ||
uint16 wormholeChainId, | ||
address initialOwner | ||
) Ownable(initialOwner) WormholeGatewayBase(wormholeRelayer, wormholeChainId) {} | ||
} |
112 changes: 112 additions & 0 deletions
112
contracts/crosschain/wormhole/WormholeGatewaySource.sol
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity ^0.8.27; | ||
|
||
import {VaaKey} from "wormhole-solidity-sdk/interfaces/IWormholeRelayer.sol"; | ||
import {toUniversalAddress, fromUniversalAddress} from "wormhole-solidity-sdk/utils/UniversalAddress.sol"; | ||
import {CAIP2} from "@openzeppelin/contracts/utils/CAIP2.sol"; | ||
import {CAIP10} from "@openzeppelin/contracts/utils/CAIP10.sol"; | ||
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; | ||
import {WormholeGatewayBase} from "./WormholeGatewayBase.sol"; | ||
import {IERC7786GatewaySource} from "../../interfaces/IERC7786.sol"; | ||
|
||
// TODO: allow non-evm destination chains via non-evm-specific finalize/retry variants | ||
abstract contract WormholeGatewaySource is IERC7786GatewaySource, WormholeGatewayBase { | ||
using Strings for *; | ||
|
||
struct PendingMessage { | ||
uint64 sequence; | ||
address sender; | ||
string destinationChain; | ||
string receiver; | ||
bytes payload; | ||
bytes[] attributes; | ||
} | ||
|
||
uint256 private _outboxId; | ||
mapping(bytes32 => PendingMessage) private _pending; | ||
|
||
event MessagePushed(bytes32 outboxId); | ||
error CannotFinalizeMessage(bytes32 outboxId); | ||
error CannotRetryMessage(bytes32 outboxId); | ||
error UnsupportedNativeTransfer(); | ||
|
||
/// @inheritdoc IERC7786GatewaySource | ||
function supportsAttribute(bytes4 /*selector*/) public pure returns (bool) { | ||
return false; | ||
} | ||
|
||
/// @inheritdoc IERC7786GatewaySource | ||
function sendMessage( | ||
string calldata destinationChain, // CAIP-2 chain identifier | ||
string calldata receiver, // CAIP-10 account address (does not include the chain identifier) | ||
bytes calldata payload, | ||
bytes[] calldata attributes | ||
) external payable returns (bytes32 outboxId) { | ||
require(msg.value == 0, UnsupportedNativeTransfer()); | ||
// Use of `if () revert` syntax to avoid accessing attributes[0] if it's empty | ||
if (attributes.length > 0) | ||
revert UnsupportedAttribute(attributes[0].length < 0x04 ? bytes4(0) : bytes4(attributes[0][0:4])); | ||
|
||
require(supportedChain(destinationChain), UnsupportedChain(destinationChain)); | ||
|
||
outboxId = bytes32(++_outboxId); | ||
_pending[outboxId] = PendingMessage(0, msg.sender, destinationChain, receiver, payload, attributes); | ||
|
||
emit MessagePosted( | ||
outboxId, | ||
CAIP10.format(CAIP2.local(), msg.sender.toChecksumHexString()), | ||
CAIP10.format(destinationChain, receiver), | ||
payload, | ||
attributes | ||
); | ||
} | ||
|
||
function quoteEvmMessage(string memory destinationChain, uint256 gasLimit) public view returns (uint256) { | ||
(uint256 cost, ) = _wormholeRelayer.quoteEVMDeliveryPrice(fromCAIP2(destinationChain), 0, gasLimit); | ||
return cost; | ||
} | ||
|
||
function quoteEvmMessage(bytes32 outboxId, uint256 gasLimit) external view returns (uint256) { | ||
return quoteEvmMessage(_pending[outboxId].destinationChain, gasLimit); | ||
} | ||
|
||
function finalizeEvmMessage(bytes32 outboxId, uint256 gasLimit) external payable { | ||
PendingMessage storage pmsg = _pending[outboxId]; | ||
|
||
require(pmsg.sender != address(0), CannotFinalizeMessage(outboxId)); | ||
|
||
uint16 wormholeDestination = fromCAIP2(pmsg.destinationChain); | ||
bytes32 remoteGateway = getRemoteGateway(pmsg.destinationChain); | ||
string memory sender = pmsg.sender.toChecksumHexString(); | ||
bytes memory adapterPayload = abi.encode(outboxId, sender, pmsg.receiver, pmsg.payload, pmsg.attributes); | ||
|
||
// TODO: potentially delete part/all of the message | ||
|
||
pmsg.sequence = _wormholeRelayer.sendPayloadToEvm{value: msg.value}( | ||
wormholeDestination, | ||
fromUniversalAddress(remoteGateway), | ||
adapterPayload, | ||
0, | ||
gasLimit | ||
); | ||
|
||
emit MessagePushed(outboxId); | ||
} | ||
|
||
// Is this necessary ? How does that work since we are not providing any additional payment ? | ||
// Is re-calling finalizeEvmMessage an alternative ? | ||
function retryEvmMessage(bytes32 outboxId, uint256 gasLimit, address newDeliveryProvider) external { | ||
PendingMessage storage pmsg = _pending[outboxId]; | ||
|
||
require(pmsg.sequence != 0, CannotRetryMessage(outboxId)); | ||
|
||
pmsg.sequence = _wormholeRelayer.resendToEvm( | ||
VaaKey(_wormholeChainId, toUniversalAddress(address(this)), pmsg.sequence), | ||
fromCAIP2(pmsg.destinationChain), | ||
0, | ||
gasLimit, | ||
newDeliveryProvider | ||
); | ||
} | ||
} |
32 changes: 32 additions & 0 deletions
32
contracts/mocks/crosschain/wormhole/WormholeRelayerMock.sol
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity ^0.8.27; | ||
|
||
import {IWormholeRelayer} from "wormhole-solidity-sdk/interfaces/IWormholeRelayer.sol"; | ||
import {IWormholeReceiver} from "wormhole-solidity-sdk/interfaces/IWormholeReceiver.sol"; | ||
import {toUniversalAddress} from "wormhole-solidity-sdk/utils/UniversalAddress.sol"; | ||
|
||
contract WormholeRelayerMock { | ||
uint64 private _seq; | ||
|
||
function sendPayloadToEvm( | ||
uint16 targetChain, | ||
address targetAddress, | ||
bytes memory payload, | ||
uint256 receiverValue, | ||
uint256 gasLimit | ||
) external payable returns (uint64) { | ||
// TODO: check that destination chain is local | ||
|
||
uint64 seq = _seq++; | ||
IWormholeReceiver(targetAddress).receiveWormholeMessages{value: receiverValue, gas: gasLimit}( | ||
payload, | ||
new bytes[](0), | ||
toUniversalAddress(msg.sender), | ||
targetChain, | ||
keccak256(abi.encode(seq)) | ||
); | ||
|
||
return seq; | ||
} | ||
} |
Submodule wormhole-solidity-sdk
added at
575181
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
@openzeppelin/contracts/=lib/@openzeppelin-contracts/contracts/ | ||
@openzeppelin/contracts-upgradeable/=lib/@openzeppelin-contracts-upgradeable/contracts/ | ||
@openzeppelin/community-contracts/=contracts/ | ||
@axelar-network/axelar-gmp-sdk-solidity/=lib/axelar-gmp-sdk-solidity/ | ||
@axelar-network/axelar-gmp-sdk-solidity/=node_modules/@axelar-network/axelar-gmp-sdk-solidity/ | ||
Amxx marked this conversation as resolved.
Show resolved
Hide resolved
|
||
@zk-email/email-tx-builder/=lib/email-tx-builder/packages/contracts/ | ||
@zk-email/contracts/=lib/zk-email-verify/packages/contracts/ | ||
wormhole-solidity-sdk/=lib/wormhole-solidity-sdk/src/ |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's do the same as #202 and flatten everything down to one contract.