Skip to content

Commit

Permalink
EVM: More transceiver stuff
Browse files Browse the repository at this point in the history
  • Loading branch information
bruce-riley committed Oct 18, 2024
1 parent 3590212 commit 191b7d2
Show file tree
Hide file tree
Showing 7 changed files with 369 additions and 358 deletions.
6 changes: 0 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@
# TODOs

- Add poc-gmp-router as a repo in here (do "forge install").
- Need to access UniversalAddress as a library.
- Need to access ITransceiver as a library.
- Need to access IRouter as a library.

# Multi-Chain Template

Expand Down
222 changes: 123 additions & 99 deletions evm/src/WormholeTransceiver.sol
Original file line number Diff line number Diff line change
@@ -1,52 +1,55 @@
// SPDX-License-Identifier: Apache 2
pragma solidity >=0.8.8 <0.9.0;

import "poc-gmp-router/interfaces/IRouter.sol";
import "poc-gmp-router/interfaces/ITransceiver.sol";

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 {
contract WormholeTransceiver is IWormholeTransceiver {
using BytesParsing for bytes;

string public constant versionString = "WormholeTransceiver-0.0.1";
address public admin;
address public pendingAdmin;

// ==================== Immutables ===============================================
uint16 public immutable ourChainId;
uint256 immutable evmChainId;
uint16 public immutable ourChain;
uint256 immutable evmChain;
IRouter public immutable router;
IWormhole public immutable wormhole;
uint8 public immutable consistencyLevel;
uint256 public immutable gasLimit;

// ==================== Constants ================================================

constructor(
uint16 _ourChainId,
uint256 _evmChainId,
uint16 _ourChain,
uint256 _evmChain,
address _admin,
address _router,
address wormholeCoreBridge,
uint8 _consistencyLevel,
uint256 _gasLimit
address _wormhole,
uint8 _consistencyLevel
) {
evmChainId = _evmChainId;
ourChainId = _ourChainId;
assert(_ourChain != 0);
assert(_evmChain != 0);
assert(_admin != address(0));
assert(_router != address(0));
assert(_wormhole != address(0));
// Not checking consistency level since maybe zero is valid?
evmChain = _evmChain;
ourChain = _ourChain;
admin = _admin;
router = IRouter(_router);
wormhole = IWormhole(wormholeCoreBridge);
wormhole = IWormhole(_wormhole);
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 ========================================
Expand All @@ -71,61 +74,82 @@ contract WormholeTransceiver is IWormholeTransceiver, ITransceiver {
/// @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.
// SPEC: MUST check that the caller is the current admin and there is not a pending transfer.
// - This is handled by onlyAdmin.

// SPEC: If possible, MUST NOT allow the admin to discard admin via this command (e.g. newAdmin != address(0) on EVM)
if (newAdmin == address(0)) {
revert InvalidAdminZeroAddress();
}

// SPEC: Immediately sets newAdmin as the admin of the integrator.
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);
// }
/// @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 newAdmin The address of the new admin.
function transferAdmin(address newAdmin) external onlyAdmin {
// SPEC: MUST check that the caller is the current admin and there is not a pending transfer.
// - This is handled by onlyAdmin.

// SPEC: Initiates the first step of a two-step process in which the current admin (to cancel) or new admin must claim.
pendingAdmin = newAdmin;
emit AdminUpdateRequested(msg.sender, newAdmin);
}

/// @notice Completes the two step process of transferring admin privileges from the current admin to another contract.
/// @dev The msg.sender must be the pending admin or the current admin contract (which cancels the transfer).
function claimAdmin() external {
// This doesn't use onlyAdmin because the pending admin must be non-zero.

// SPEC: MUST check that the caller is the current admin OR the pending admin.
if ((admin != msg.sender) && (pendingAdmin != msg.sender)) {
revert CallerNotAdmin();
}

// SPEC: MUST check that there is an admin transfer pending (e. g. pendingAdmin != address(0) on EVM).
if (pendingAdmin == address(0)) {
revert NoAdminUpdatePending();
}

// SPEC: Cancels / Completes the second step of the two-step transfer. Sets the admin to the caller and clears the pending admin.
address oldAdmin = admin;
admin = msg.sender;
pendingAdmin = address(0);
emit AdminUpdated(oldAdmin, msg.sender);
}

/// @notice Sets the admin contract to null, making the configuration immutable. THIS IS NOT REVERSIBLE.
/// @dev The msg.sender must be the current admin contract.
function discardAdmin() external onlyAdmin {
// SPEC: MUST check that the caller is the current admin and there is not a pending transfer.
// - This is handled by onlyAdmin.

// SPEC: Clears the current admin. THIS IS NOT REVERSIBLE. This ensures that the Integrator configuration becomes immutable.
admin = address(0);
emit AdminDiscarded(msg.sender);
}

/// @inheritdoc IWormholeTransceiver
function setWormholePeer(uint16 peerChainId, bytes32 peerContract) external payable onlyAdmin {
if (peerChainId == 0) {
revert InvalidWormholeChainIdZero();
function setWormholePeer(uint16 peerChain, bytes32 peerContract) external payable onlyAdmin {
if (peerChain == 0) {
revert InvalidWormholeChainZero();
}
if (peerContract == bytes32(0)) {
revert InvalidWormholePeerZeroAddress();
}

bytes32 oldPeerContract = _getWormholePeersStorage()[peerChainId];
bytes32 oldPeerContract = _getWormholePeersStorage()[peerChain];

// 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
// SPEC: MUST not set the peer if it is already set.
if (oldPeerContract != bytes32(0)) {
revert PeerAlreadySet(peerChainId, oldPeerContract);
revert PeerAlreadySet(peerChain, oldPeerContract);
}

_getWormholePeersStorage()[peerChainId] = peerContract;
emit SetWormholePeer(peerChainId, peerContract);
_getWormholePeersStorage()[peerChain] = peerContract;
emit SetWormholePeer(peerChain, peerContract);
}

// =============== Interface ===============================================================
Expand All @@ -136,21 +160,21 @@ contract WormholeTransceiver is IWormholeTransceiver, ITransceiver {
}

/// @inheritdoc ITransceiver
function quoteDeliveryPrice(uint16 destinationChainId) external view returns (uint256) {
return _quoteDeliveryPrice(destinationChainId);
function quoteDeliveryPrice(uint16 dstChain) external view returns (uint256) {
return _quoteDeliveryPrice(dstChain);
}

/// @inheritdoc ITransceiver
/// @dev The caller should set the delivery price in msg.value.
function sendMessage(
UniversalAddress sourceAddress,
uint16 recipientChain,
UniversalAddress recipientAddress,
UniversalAddress srcAddr,
uint16 dstChain,
UniversalAddress dstAddr,
uint64 sequence,
bytes32 payloadHash,
bytes32 refundAddress
bytes32 refundAddr
) external payable {
_sendMessage(sourceAddress, recipientChain, recipientAddress, sequence, payloadHash, refundAddress);
_sendMessage(srcAddr, dstChain, dstAddr, sequence, payloadHash, refundAddr);
}

/// @inheritdoc IWormholeTransceiver
Expand All @@ -160,103 +184,100 @@ contract WormholeTransceiver is IWormholeTransceiver, ITransceiver {

// ============= Internal ===============================================================

function _quoteDeliveryPrice(uint16 /*destinationChainId*/ ) internal view returns (uint256) {
function _quoteDeliveryPrice(uint16 /*dstChain*/ ) internal view returns (uint256) {
return wormhole.messageFee();
}

function _sendMessage(
UniversalAddress sourceAddress,
uint16 recipientChain,
UniversalAddress recipientAddress,
UniversalAddress srcAddr,
uint16 dstChain,
UniversalAddress dstAddr,
uint64 sequence,
bytes32 payloadHash,
bytes32 // refundAddress
bytes32 // refundAddr
) internal {
bytes memory payload = _encodePayload(sourceAddress, sequence, recipientChain, recipientAddress, payloadHash);
bytes memory payload = _encodePayload(srcAddr, sequence, dstChain, dstAddr, payloadHash);
wormhole.publishMessage{value: msg.value}(0, payload, consistencyLevel);
emit SendTransceiverMessage(sourceAddress, recipientChain, recipientAddress, sequence, payloadHash);
emit SendTransceiverMessage(srcAddr, dstChain, dstAddr, sequence, payloadHash);
}

function _receiveMessage(bytes memory encodedMessage) internal {
uint16 sourceChainId;
bytes memory payload;
(sourceChainId, payload) = _verifyMessage(encodedMessage);
// Verify the wormhole message and extract the source chain and payload.
(uint16 srcChain, bytes memory payload) = _verifyMessage(encodedMessage);

(
UniversalAddress sourceAddress,
uint64 sequence,
uint16 recipientChain,
UniversalAddress recipientAddress,
bytes32 payloadHash
) = _decodePayload(payload);
// Decode our payload.
(UniversalAddress srcAddr, uint64 sequence, uint16 dstChain, UniversalAddress dstAddr, bytes32 payloadHash) =
_decodePayload(payload);

if (recipientChain != ourChainId) {
revert InvalidChainId(recipientChain);
// Make sure this payload is meant for us.
if (dstChain != ourChain) {
revert InvalidChain(dstChain);
}

router.attestMessage(sourceChainId, sourceAddress, sequence, ourChainId, recipientAddress, payloadHash);
// Post the message to the router.
router.attestMessage(srcChain, srcAddr, sequence, ourChain, dstAddr, payloadHash);

// We don't need to emit an event here because _verifyMessage already did.
}

function _encodePayload(
UniversalAddress sourceAddress,
UniversalAddress srcAddr,
uint64 sequence,
uint16 recipientChain,
UniversalAddress recipientAddress,
uint16 dstChain,
UniversalAddress dstAddr,
bytes32 payloadHash
) internal pure returns (bytes memory payload) {
return abi.encodePacked(sourceAddress, sequence, recipientChain, recipientAddress, payloadHash);
return abi.encodePacked(srcAddr, sequence, dstChain, dstAddr, payloadHash);
}

function _decodePayload(bytes memory payload)
internal
pure
returns (
UniversalAddress sourceAddress,
UniversalAddress srcAddr,
uint64 sequence,
uint16 recipientChain,
UniversalAddress recipientAddress,
uint16 dstChain,
UniversalAddress dstAddr,
bytes32 payloadHash
)
{
bytes32 b32;
uint256 offset = 0;

(b32, offset) = payload.asBytes32(offset);
sourceAddress = UniversalAddressLibrary.fromBytes32(b32);
srcAddr = UniversalAddressLibrary.fromBytes32(b32);

(sequence, offset) = payload.asUint64(offset);
(recipientChain, offset) = payload.asUint16(offset);
(dstChain, offset) = payload.asUint16(offset);

(b32, offset) = payload.asBytes32(offset);
recipientAddress = UniversalAddressLibrary.fromBytes32(b32);
dstAddr = 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
// 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
// Ensure that the message came from the 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);
checkFork(evmChain);
return getWormholePeer(vm.emitterChainId) == vm.emitterAddress;
}

Expand All @@ -272,6 +293,9 @@ contract WormholeTransceiver is IWormholeTransceiver, ITransceiver {
if (admin != msg.sender) {
revert CallerNotAdmin();
}
if (pendingAdmin != address(0)) {
revert AdminTransferPending();
}
_;
}
}
Loading

0 comments on commit 191b7d2

Please sign in to comment.