generated from evan-gray/multichain-template
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
457dfce
commit fd7698d
Showing
11 changed files
with
962 additions
and
39 deletions.
There are no files selected for viewing
This file contains 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 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 |
---|---|---|
|
@@ -12,3 +12,6 @@ docs/ | |
|
||
# Dotenv file | ||
.env | ||
|
||
# Code coverage | ||
lcov.info |
This file was deleted.
Oops, something went wrong.
This file contains 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,277 @@ | ||
// SPDX-License-Identifier: Apache 2 | ||
pragma solidity >=0.8.8 <0.9.0; | ||
|
||
import "wormhole-solidity-sdk/libraries/BytesParsing.sol"; | ||
import "wormhole-solidity-sdk/interfaces/IWormhole.sol"; | ||
|
||
import "./interfaces/IRouter.sol"; | ||
import "./interfaces/ITransceiver.sol"; | ||
import "./interfaces/IWormholeTransceiver.sol"; | ||
import "./libraries/TransceiverHelpers.sol"; | ||
|
||
contract WormholeTransceiver is IWormholeTransceiver, ITransceiver { | ||
using BytesParsing for bytes; | ||
|
||
string public constant versionString = "WormholeTransceiver-0.0.1"; | ||
address public admin; | ||
|
||
// ==================== Immutables =============================================== | ||
uint16 public immutable ourChainId; | ||
uint256 immutable evmChainId; | ||
IRouter public immutable router; | ||
IWormhole public immutable wormhole; | ||
uint8 public immutable consistencyLevel; | ||
uint256 public immutable gasLimit; | ||
|
||
// ==================== Constants ================================================ | ||
|
||
constructor( | ||
uint16 _ourChainId, | ||
uint256 _evmChainId, | ||
address _admin, | ||
address _router, | ||
address wormholeCoreBridge, | ||
uint8 _consistencyLevel, | ||
uint256 _gasLimit | ||
) { | ||
evmChainId = _evmChainId; | ||
ourChainId = _ourChainId; | ||
admin = _admin; | ||
router = IRouter(_router); | ||
wormhole = IWormhole(wormholeCoreBridge); | ||
consistencyLevel = _consistencyLevel; | ||
gasLimit = _gasLimit; | ||
} | ||
|
||
// =============== Storage =============================================== | ||
|
||
bytes32 private constant WORMHOLE_CONSUMED_VAAS_SLOT = bytes32(uint256(keccak256("whTransceiver.consumedVAAs")) - 1); | ||
|
||
bytes32 private constant WORMHOLE_PEERS_SLOT = bytes32(uint256(keccak256("whTransceiver.peers")) - 1); | ||
|
||
// =============== Storage Setters/Getters ======================================== | ||
|
||
function _getWormholePeersStorage() internal pure returns (mapping(uint16 => bytes32) storage $) { | ||
uint256 slot = uint256(WORMHOLE_PEERS_SLOT); | ||
assembly ("memory-safe") { | ||
$.slot := slot | ||
} | ||
} | ||
|
||
// =============== Public Getters ====================================================== | ||
|
||
/// @inheritdoc IWormholeTransceiver | ||
function getWormholePeer(uint16 chainId) public view returns (bytes32) { | ||
return _getWormholePeersStorage()[chainId]; | ||
} | ||
|
||
// =============== Admin =============================================================== | ||
|
||
/// @notice Transfers admin privileges from the current admin to another contract. | ||
/// @dev The msg.sender must be the current admin contract. | ||
/// @param newAdmin The address of the new admin. | ||
function updateAdmin(address newAdmin) external onlyAdmin { | ||
// Update the storage. | ||
admin = newAdmin; | ||
emit AdminUpdated(msg.sender, newAdmin); | ||
} | ||
|
||
// /// @notice Starts the two step process of transferring admin privileges from the current admin to another contract. | ||
// /// @dev The msg.sender must be the current admin contract. | ||
// /// @param integrator The address of the integrator contract. | ||
// /// @param newAdmin The address of the new admin. | ||
// function transferAdmin(address newAdmin) external onlyAdmin { | ||
// // Get the storage for this integrator contract | ||
// mapping(address => IntegratorConfig) storage integratorConfigs = _getIntegratorConfigsStorage(); | ||
|
||
// // Update the storage with this request. | ||
// integratorConfigs[integrator].transfer = newAdmin; | ||
// emit AdminUpdateRequested(integrator, msg.sender, newAdmin); | ||
// } | ||
|
||
// /// @notice Starts the two step process of transferring admin privileges from the current admin to another contract. | ||
// /// @dev The msg.sender must be the current admin contract. | ||
// /// @param integrator The address of the integrator contract. | ||
// function claimAdmin(address integrator) external { | ||
// // Get the storage for this integrator contract | ||
// mapping(address => IntegratorConfig) storage integratorConfigs = _getIntegratorConfigsStorage(); | ||
|
||
// address oldAdmin = integratorConfigs[integrator].admin; | ||
// address newAdmin = integratorConfigs[integrator].transfer; | ||
// if ((msg.sender == oldAdmin) || (msg.sender == newAdmin)) { | ||
// // Update the storage with this request. | ||
// integratorConfigs[integrator].admin = newAdmin; | ||
// integratorConfigs[integrator].transfer = address(0); | ||
// } | ||
// emit AdminUpdated(integrator, oldAdmin, newAdmin); | ||
// } | ||
|
||
/// @inheritdoc IWormholeTransceiver | ||
function setWormholePeer(uint16 peerChainId, bytes32 peerContract) external payable onlyAdmin { | ||
if (peerChainId == 0) { | ||
revert InvalidWormholeChainIdZero(); | ||
} | ||
if (peerContract == bytes32(0)) { | ||
revert InvalidWormholePeerZeroAddress(); | ||
} | ||
|
||
bytes32 oldPeerContract = _getWormholePeersStorage()[peerChainId]; | ||
|
||
// We don't want to allow updating a peer since this adds complexity in the accountant | ||
// If the owner makes a mistake with peer registration they should deploy a new Wormhole | ||
// transceiver and register this new transceiver with the Router | ||
if (oldPeerContract != bytes32(0)) { | ||
revert PeerAlreadySet(peerChainId, oldPeerContract); | ||
} | ||
|
||
_getWormholePeersStorage()[peerChainId] = peerContract; | ||
emit SetWormholePeer(peerChainId, peerContract); | ||
} | ||
|
||
// =============== Interface =============================================================== | ||
|
||
/// @inheritdoc ITransceiver | ||
function getTransceiverType() external pure returns (string memory) { | ||
return versionString; | ||
} | ||
|
||
/// @inheritdoc ITransceiver | ||
function quoteDeliveryPrice(uint16 destinationChainId) external view returns (uint256) { | ||
return _quoteDeliveryPrice(destinationChainId); | ||
} | ||
|
||
/// @inheritdoc ITransceiver | ||
/// @dev The caller should set the delivery price in msg.value. | ||
function sendMessage( | ||
UniversalAddress sourceAddress, | ||
uint16 recipientChain, | ||
UniversalAddress recipientAddress, | ||
uint64 sequence, | ||
bytes32 payloadHash, | ||
bytes32 refundAddress | ||
) external payable { | ||
_sendMessage(sourceAddress, recipientChain, recipientAddress, sequence, payloadHash, refundAddress); | ||
} | ||
|
||
/// @inheritdoc IWormholeTransceiver | ||
function receiveMessage(bytes memory encodedMessage) external { | ||
_receiveMessage(encodedMessage); | ||
} | ||
|
||
// ============= Internal =============================================================== | ||
|
||
function _quoteDeliveryPrice(uint16 /*destinationChainId*/ ) internal view returns (uint256) { | ||
return wormhole.messageFee(); | ||
} | ||
|
||
function _sendMessage( | ||
UniversalAddress sourceAddress, | ||
uint16 recipientChain, | ||
UniversalAddress recipientAddress, | ||
uint64 sequence, | ||
bytes32 payloadHash, | ||
bytes32 // refundAddress | ||
) internal { | ||
bytes memory payload = _encodePayload(sourceAddress, sequence, recipientChain, recipientAddress, payloadHash); | ||
wormhole.publishMessage{value: msg.value}(0, payload, consistencyLevel); | ||
emit SendTransceiverMessage(sourceAddress, recipientChain, recipientAddress, sequence, payloadHash); | ||
} | ||
|
||
function _receiveMessage(bytes memory encodedMessage) internal { | ||
uint16 sourceChainId; | ||
bytes memory payload; | ||
(sourceChainId, payload) = _verifyMessage(encodedMessage); | ||
|
||
( | ||
UniversalAddress sourceAddress, | ||
uint64 sequence, | ||
uint16 recipientChain, | ||
UniversalAddress recipientAddress, | ||
bytes32 payloadHash | ||
) = _decodePayload(payload); | ||
|
||
if (recipientChain != ourChainId) { | ||
revert InvalidChainId(recipientChain); | ||
} | ||
|
||
router.attestMessage(sourceChainId, sourceAddress, sequence, ourChainId, recipientAddress, payloadHash); | ||
} | ||
|
||
function _encodePayload( | ||
UniversalAddress sourceAddress, | ||
uint64 sequence, | ||
uint16 recipientChain, | ||
UniversalAddress recipientAddress, | ||
bytes32 payloadHash | ||
) internal pure returns (bytes memory payload) { | ||
return abi.encodePacked(sourceAddress, sequence, recipientChain, recipientAddress, payloadHash); | ||
} | ||
|
||
function _decodePayload(bytes memory payload) | ||
internal | ||
pure | ||
returns ( | ||
UniversalAddress sourceAddress, | ||
uint64 sequence, | ||
uint16 recipientChain, | ||
UniversalAddress recipientAddress, | ||
bytes32 payloadHash | ||
) | ||
{ | ||
bytes32 b32; | ||
uint256 offset = 0; | ||
|
||
(b32, offset) = payload.asBytes32(offset); | ||
sourceAddress = UniversalAddressLibrary.fromBytes32(b32); | ||
|
||
(sequence, offset) = payload.asUint64(offset); | ||
(recipientChain, offset) = payload.asUint16(offset); | ||
|
||
(b32, offset) = payload.asBytes32(offset); | ||
recipientAddress = UniversalAddressLibrary.fromBytes32(b32); | ||
|
||
(payloadHash, offset) = payload.asBytes32(offset); | ||
|
||
_checkLength(payload, offset); | ||
} | ||
|
||
function _verifyMessage(bytes memory encodedMessage) internal returns (uint16, bytes memory) { | ||
// verify VAA against Wormhole Core Bridge contract | ||
(IWormhole.VM memory vm, bool valid, string memory reason) = wormhole.parseAndVerifyVM(encodedMessage); | ||
|
||
// ensure that the VAA is valid | ||
if (!valid) { | ||
revert InvalidVaa(reason); | ||
} | ||
|
||
// ensure that the message came from a registered peer contract | ||
if (!_verifyPeer(vm)) { | ||
revert InvalidWormholePeer(vm.emitterChainId, vm.emitterAddress); | ||
} | ||
|
||
// emit `ReceivedMessage` event | ||
emit ReceivedMessage(vm.hash, vm.emitterChainId, vm.emitterAddress, vm.sequence); | ||
|
||
return (vm.emitterChainId, vm.payload); | ||
} | ||
|
||
function _verifyPeer(IWormhole.VM memory vm) internal view returns (bool) { | ||
checkFork(evmChainId); | ||
return getWormholePeer(vm.emitterChainId) == vm.emitterAddress; | ||
} | ||
|
||
function _checkLength(bytes memory payload, uint256 expected) private pure { | ||
if (payload.length != expected) { | ||
revert InvalidPayloadLength(payload.length, expected); | ||
} | ||
} | ||
|
||
// =============== MODIFIERS =============================================== | ||
|
||
modifier onlyAdmin() { | ||
if (admin != msg.sender) { | ||
revert CallerNotAdmin(); | ||
} | ||
_; | ||
} | ||
} |
This file contains 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,52 @@ | ||
// SPDX-License-Identifier: Apache-2.0 | ||
pragma solidity ^0.8.13; | ||
|
||
import "../libraries/UniversalAddress.sol"; | ||
|
||
interface IRouter { | ||
/// @notice Send a message to another chain. | ||
/// @param recipientChain The Wormhole chain ID of the recipient. | ||
/// @param recipientAddress The universal address of the peer on the recipient chain. | ||
/// @param refundAddress The source chain refund address passed to the Transceiver. | ||
/// @param payloadHash keccak256 of a message to be sent to the recipient chain. | ||
/// @return uint64 The sequence number of the message. | ||
function sendMessage( | ||
uint16 recipientChain, | ||
UniversalAddress recipientAddress, | ||
address refundAddress, | ||
bytes32 payloadHash | ||
) external payable returns (uint64); | ||
|
||
/// @notice Called by a Transceiver contract to attest to a message. | ||
/// @param sourceChainId The Wormhole chain ID of the sender. | ||
/// @param sourceAddress The universal address of the peer on the sending chain. | ||
/// @param sequence The sequence number of the message (per integrator). | ||
/// @param destinationChainId The Wormhole chain ID of the destination. | ||
/// @param destinationAddress The destination address of the message. | ||
/// @param payloadHash The keccak256 of payload from the integrator. | ||
function attestMessage( | ||
uint16 sourceChainId, | ||
UniversalAddress sourceAddress, | ||
uint64 sequence, | ||
uint16 destinationChainId, | ||
UniversalAddress destinationAddress, | ||
bytes32 payloadHash | ||
) external; | ||
|
||
/// @notice Called by a integrator contract to receive a message and mark it executed. | ||
/// @param sourceChainId The Wormhole chain ID of the sender. | ||
/// @param sourceAddress The universal address of the peer on the sending chain. | ||
/// @param sequence The sequence number of the message (per integrator). | ||
/// @param destinationChainId The Wormhole chain ID of the destination. | ||
/// @param destinationAddress The destination address of the message. | ||
/// @param payloadHash The keccak256 of payload from the integrator. | ||
/// @return uint128 The bitmap | ||
function receiveMessage( | ||
uint16 sourceChainId, | ||
UniversalAddress sourceAddress, | ||
uint64 sequence, | ||
uint16 destinationChainId, | ||
UniversalAddress destinationAddress, | ||
bytes32 payloadHash | ||
) external payable returns (uint128, uint128); | ||
} |
Oops, something went wrong.