-
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.
+476
−2
Open
Wormhole adaptor #94
Changes from all 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,118 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity ^0.8.27; | ||
|
||
import {IWormholeRelayer} from "wormhole-solidity-sdk/interfaces/IWormholeRelayer.sol"; | ||
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; | ||
import {InteroperableAddress} from "@openzeppelin/contracts/utils/draft-InteroperableAddress.sol"; | ||
|
||
/// Note: only EVM chains are currently supported | ||
abstract contract WormholeGatewayBase is Ownable { | ||
using InteroperableAddress for bytes; | ||
|
||
IWormholeRelayer internal immutable _wormholeRelayer; | ||
uint16 internal immutable _wormholeChainId; | ||
uint24 private constant MASK = 1 << 16; | ||
|
||
// Remote gateway. | ||
mapping(uint256 chainId => address) private _remoteGateways; | ||
|
||
// chain equivalence ChainId <> Wormhole | ||
mapping(uint256 chainId => uint24 wormholeId) private _chainIdToWormhole; | ||
mapping(uint16 wormholeId => uint256 chainId) private _wormholeToChainId; | ||
|
||
/// @dev A remote gateway has been registered for a chain. | ||
event RegisteredRemoteGateway(uint256 chainId, address remote); | ||
|
||
/// @dev A chain equivalence has been registered. | ||
event RegisteredChainEquivalence(uint256 chainId, uint16 wormholeId); | ||
|
||
error UnsupportedChainId(uint256 chainId); | ||
error UnsupportedWormholeChain(uint16 wormholeId); | ||
error ChainEquivalenceAlreadyRegistered(uint256 chainId, uint16 wormhole); | ||
error RemoteGatewayAlreadyRegistered(uint256 chainId); | ||
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(bytes memory chain) public view virtual returns (bool) { | ||
(bool success, uint256 chainId, ) = chain.tryParseEvmV1(); | ||
return success && supportedChain(chainId); | ||
} | ||
|
||
function supportedChain(uint256 chainId) public view virtual returns (bool) { | ||
return _chainIdToWormhole[chainId] & MASK == MASK; | ||
} | ||
|
||
function getWormholeChain(bytes memory chain) public view virtual returns (uint16) { | ||
(uint256 chainId, ) = chain.parseEvmV1(); | ||
return getWormholeChain(chainId); | ||
} | ||
|
||
function getWormholeChain(uint256 chainId) public view virtual returns (uint16) { | ||
uint24 wormholeId = _chainIdToWormhole[chainId]; | ||
require(wormholeId & MASK == MASK, UnsupportedChainId(chainId)); | ||
return uint16(wormholeId); | ||
} | ||
|
||
function getChainId(uint16 wormholeId) public view virtual returns (uint256) { | ||
uint256 chainId = _wormholeToChainId[wormholeId]; | ||
require(chainId != 0, UnsupportedWormholeChain(wormholeId)); | ||
return chainId; | ||
} | ||
|
||
/// @dev Returns the address of the remote gateway for a given chainType and chainReference. | ||
function getRemoteGateway(bytes memory chain) public view virtual returns (address) { | ||
(uint256 chainId, ) = chain.parseEvmV1(); | ||
return getRemoteGateway(chainId); | ||
} | ||
|
||
function getRemoteGateway(uint256 chainId) public view virtual returns (address) { | ||
address addr = _remoteGateways[chainId]; | ||
require(addr != address(0), UnsupportedChainId(chainId)); | ||
return addr; | ||
} | ||
|
||
function registerChainEquivalence( | ||
bytes calldata chain, | ||
uint16 wormholeId | ||
) public virtual /*onlyOwner in registerChainEquivalence*/ { | ||
(uint256 chainId, ) = chain.parseEvmV1Calldata(); | ||
registerChainEquivalence(chainId, wormholeId); | ||
} | ||
|
||
function registerChainEquivalence(uint256 chainId, uint16 wormholeId) public virtual onlyOwner { | ||
require( | ||
_chainIdToWormhole[chainId] == 0 && _wormholeToChainId[wormholeId] == 0, | ||
ChainEquivalenceAlreadyRegistered(chainId, wormholeId) | ||
); | ||
|
||
_chainIdToWormhole[chainId] = wormholeId | MASK; | ||
_wormholeToChainId[wormholeId] = chainId; | ||
emit RegisteredChainEquivalence(chainId, wormholeId); | ||
} | ||
|
||
function registerRemoteGateway(bytes calldata remote) public virtual /*onlyOwner in registerRemoteGateway*/ { | ||
(uint256 chainId, address addr) = remote.parseEvmV1Calldata(); | ||
registerRemoteGateway(chainId, addr); | ||
} | ||
|
||
function registerRemoteGateway(uint256 chainId, address addr) public virtual onlyOwner { | ||
require(supportedChain(chainId), UnsupportedChainId(chainId)); | ||
require(_remoteGateways[chainId] == address(0), RemoteGatewayAlreadyRegistered(chainId)); | ||
_remoteGateways[chainId] = addr; | ||
emit RegisteredRemoteGateway(chainId, addr); | ||
} | ||
} |
55 changes: 55 additions & 0 deletions
55
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,55 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity ^0.8.27; | ||
|
||
import {IWormholeReceiver} from "wormhole-solidity-sdk/interfaces/IWormholeReceiver.sol"; | ||
import {fromUniversalAddress} from "wormhole-solidity-sdk/utils/UniversalAddress.sol"; | ||
import {BitMaps} from "@openzeppelin/contracts/utils/structs/BitMaps.sol"; | ||
import {InteroperableAddress} from "@openzeppelin/contracts/utils/draft-InteroperableAddress.sol"; | ||
import {IERC7786Receiver} from "../../interfaces/IERC7786.sol"; | ||
import {WormholeGatewayBase} from "./WormholeGatewayBase.sol"; | ||
|
||
abstract contract WormholeGatewayDestination is WormholeGatewayBase, IWormholeReceiver { | ||
using BitMaps for BitMaps.BitMap; | ||
using InteroperableAddress for bytes; | ||
|
||
BitMaps.BitMap private _executed; | ||
|
||
error InvalidOriginGateway(uint16 wormholeSourceChain, 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 { | ||
require(additionalMessages.length == 0, AdditionalMessagesNotSupported()); | ||
|
||
(bytes32 outboxId, bytes memory sender, bytes memory recipient, bytes memory payload) = abi.decode( | ||
adapterPayload, | ||
(bytes32, bytes, bytes, bytes) | ||
); | ||
|
||
// Wormhole to ERC-7930 translation | ||
address addr = getRemoteGateway(getChainId(wormholeSourceChain)); | ||
|
||
// check message validity | ||
// - `wormholeSourceAddress` is the remote gateway on the origin chain. | ||
require( | ||
addr == fromUniversalAddress(wormholeSourceAddress), | ||
InvalidOriginGateway(wormholeSourceChain, wormholeSourceAddress) | ||
); | ||
|
||
// 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)); | ||
|
||
(, address target) = recipient.parseEvmV1(); | ||
bytes4 result = IERC7786Receiver(target).receiveMessage(deliveryHash, sender, payload); | ||
require(result == IERC7786Receiver.receiveMessage.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) {} | ||
} |
100 changes: 100 additions & 0 deletions
100
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,100 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity ^0.8.27; | ||
|
||
import {VaaKey} from "wormhole-solidity-sdk/interfaces/IWormholeRelayer.sol"; | ||
import {toUniversalAddress} from "wormhole-solidity-sdk/utils/UniversalAddress.sol"; | ||
import {InteroperableAddress} from "@openzeppelin/contracts/utils/draft-InteroperableAddress.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 InteroperableAddress for bytes; | ||
// using Strings for *; | ||
|
||
struct PendingMessage { | ||
bool pending; | ||
address sender; | ||
uint256 value; | ||
bytes recipient; | ||
bytes payload; | ||
} | ||
|
||
uint256 private _sendId; | ||
mapping(bytes32 => PendingMessage) private _pending; | ||
|
||
event MessageRelayed(bytes32 sendId); | ||
error InvalidSendId(bytes32 sendId); | ||
|
||
/// @inheritdoc IERC7786GatewaySource | ||
function supportsAttribute(bytes4 /*selector*/) public pure returns (bool) { | ||
return false; | ||
} | ||
|
||
/// @inheritdoc IERC7786GatewaySource | ||
function sendMessage( | ||
bytes calldata recipient, // Binary Interoperable Address | ||
bytes calldata payload, | ||
bytes[] calldata attributes | ||
) external payable returns (bytes32 sendId) { | ||
// 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])); | ||
|
||
// Note: this reverts with UnsupportedChainId if the recipient is not on a supported chain. | ||
// No real need to check the return value. | ||
getRemoteGateway(recipient); | ||
|
||
sendId = bytes32(++_sendId); | ||
_pending[sendId] = PendingMessage(true, msg.sender, msg.value, recipient, payload); | ||
|
||
emit MessageSent( | ||
sendId, | ||
InteroperableAddress.formatEvmV1(block.chainid, msg.sender), | ||
recipient, | ||
payload, | ||
0, | ||
attributes | ||
); | ||
} | ||
|
||
function quoteRelay( | ||
bytes calldata recipient, // Binary Interoperable Address | ||
bytes calldata /*payload*/, | ||
bytes[] calldata /*attributes*/, | ||
uint256 value, | ||
uint256 gasLimit, | ||
address /*refundRecipient*/ | ||
) external view returns (uint256) { | ||
(uint256 cost, ) = _wormholeRelayer.quoteEVMDeliveryPrice(getWormholeChain(recipient), value, gasLimit); | ||
return cost - value; | ||
} | ||
|
||
function requestRelay(bytes32 sendId, uint256 gasLimit, address /*refundRecipient*/) external payable { | ||
// TODO: revert if refundRecipient is not address(0)? | ||
|
||
PendingMessage memory pmsg = _pending[sendId]; | ||
require(pmsg.pending, InvalidSendId(sendId)); | ||
|
||
// Do we want to do that to get a gas refund? Would it be valuable to keep that information stored? | ||
delete _pending[sendId]; | ||
|
||
// TODO: Do we care about the returned "sequence"? | ||
_wormholeRelayer.sendPayloadToEvm{value: pmsg.value + msg.value}( | ||
getWormholeChain(pmsg.recipient), | ||
getRemoteGateway(pmsg.recipient), | ||
abi.encode( | ||
sendId, | ||
InteroperableAddress.formatEvmV1(block.chainid, pmsg.sender), | ||
pmsg.recipient, | ||
pmsg.payload | ||
), | ||
pmsg.value, | ||
gasLimit | ||
); | ||
|
||
emit MessageRelayed(sendId); | ||
} | ||
} |
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 @openzeppelin-contracts
updated
236 files
Submodule @openzeppelin-contracts-upgradeable
updated
147 files
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
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.