Skip to content

Commit ad595e3

Browse files
committed
evm: Add SharedWormholeTransceiver
1 parent 2d9d13b commit ad595e3

File tree

4 files changed

+1101
-0
lines changed

4 files changed

+1101
-0
lines changed
Lines changed: 347 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,347 @@
1+
// SPDX-License-Identifier: Apache 2
2+
pragma solidity ^0.8.19;
3+
4+
import "wormhole-solidity-sdk/libraries/BytesParsing.sol";
5+
import "wormhole-solidity-sdk/interfaces/IWormhole.sol";
6+
import "wormhole-solidity-sdk/Utils.sol";
7+
8+
import "../../interfaces/IMsgReceiver.sol";
9+
import "../../interfaces/ISharedWormholeTransceiver.sol";
10+
11+
string constant sharedWormholeTransceiverVersionString = "SharedWormholeTransceiver-0.0.1";
12+
13+
/// @title SharedWormholeTransceiver
14+
///
15+
/// @author Wormhole Project Contributors.
16+
///
17+
/// @notice The SharedWormholeTransceiver is a Wormhole transceiver implementation that can
18+
/// be shared between multiple managers. It implements the ITransceiver interface.
19+
///
20+
/// The SharedWormholeTransceiver assumes the use of the Executor at the manager level,
21+
/// so it has no internal relayer support. It currently requires no transceiver instructions,
22+
/// so the parameter required by the interface is not used.
23+
///
24+
/// Since the ITransceiver interface requires NttManager and token addresses, this transceiver
25+
/// implements those interfaces, but they are not used, and the values returned are zero.
26+
///
27+
/// Since this transceiver is not owned by a specific manager, some of the interface
28+
/// functions are stubbed off (transferring ownership, for instance). Additionally,
29+
/// this transceiver is immutable, so the upgrade function reverts.
30+
///
31+
/// This transceiver has an admin who is responsible for provisioning peers. There are
32+
/// a number of admin functions for provisioning peers and transferring the admin.
33+
/// Additionally, there is a function to discard the admin, making the contract
34+
/// truly immutable.
35+
///
36+
/// This transceiver maintains the Wormhole transceiver wire format, so in theory
37+
/// it should be able to peer with instances of the standard `WormholeTransceiver`.
38+
///
39+
contract SharedWormholeTransceiver is ISharedWormholeTransceiver {
40+
using BytesParsing for bytes; // Used by _decodePayload
41+
42+
// ==================== Constants ================================================
43+
// TODO: These are in `WormholeTranceiverState.sol` but I can't access them for some reason.
44+
// TODO: Do we need to publish `WH_TRANSCEIVER_INIT_PREFIX` and `WH_PEER_REGISTRATION_PREFIX`?
45+
46+
/// @dev Prefix for all TransceiverMessage payloads
47+
/// @notice Magic string (constant value set by messaging provider) that idenfies the payload as an transceiver-emitted payload.
48+
/// Note that this is not a security critical field. It's meant to be used by messaging providers to identify which messages are Transceiver-related.
49+
bytes4 public constant WH_TRANSCEIVER_PAYLOAD_PREFIX = 0x9945FF10;
50+
51+
/// @dev Prefix for all Wormhole transceiver initialisation payloads
52+
/// This is bytes4(keccak256("WormholeTransceiverInit"))
53+
bytes4 constant WH_TRANSCEIVER_INIT_PREFIX = 0x9c23bd3b;
54+
55+
/// @dev Prefix for all Wormhole peer registration payloads
56+
/// This is bytes4(keccak256("WormholePeerRegistration"))
57+
bytes4 constant WH_PEER_REGISTRATION_PREFIX = 0x18fc67c2;
58+
59+
// ==================== Immutables ===============================================
60+
61+
address public admin;
62+
address public pendingAdmin;
63+
uint16 public immutable ourChain;
64+
IWormhole public immutable wormhole;
65+
uint8 public immutable consistencyLevel;
66+
67+
// ==================== Constructor ==============================================
68+
69+
constructor(uint16 _ourChain, address _admin, address _wormhole, uint8 _consistencyLevel) {
70+
assert(_ourChain != 0);
71+
assert(_admin != address(0));
72+
assert(_wormhole != address(0));
73+
// Not checking consistency level since maybe zero is valid?
74+
ourChain = _ourChain;
75+
admin = _admin;
76+
wormhole = IWormhole(_wormhole);
77+
consistencyLevel = _consistencyLevel;
78+
}
79+
80+
// =============== Storage Keys =============================================
81+
82+
bytes32 private constant WORMHOLE_PEERS_SLOT = bytes32(uint256(keccak256("swt.peers")) - 1);
83+
bytes32 private constant CHAINS_SLOT = bytes32(uint256(keccak256("swt.chains")) - 1);
84+
85+
// =============== Storage Accessors ========================================
86+
87+
function _getPeersStorage() internal pure returns (mapping(uint16 => bytes32) storage $) {
88+
uint256 slot = uint256(WORMHOLE_PEERS_SLOT);
89+
assembly ("memory-safe") {
90+
$.slot := slot
91+
}
92+
}
93+
94+
function _getChainsStorage() internal pure returns (uint16[] storage $) {
95+
uint256 slot = uint256(CHAINS_SLOT);
96+
assembly ("memory-safe") {
97+
$.slot := slot
98+
}
99+
}
100+
101+
// =============== Public Getters ======================================================
102+
103+
/// @inheritdoc ISharedWormholeTransceiver
104+
function getPeer(
105+
uint16 chainId
106+
) public view returns (bytes32 peerContract) {
107+
peerContract = _getPeersStorage()[chainId];
108+
if (peerContract == bytes32(0)) {
109+
revert UnregisteredPeer(chainId);
110+
}
111+
}
112+
113+
/// @inheritdoc ISharedWormholeTransceiver
114+
function getPeers() public view returns (PeerEntry[] memory results) {
115+
uint16[] storage chains = _getChainsStorage();
116+
uint256 len = chains.length;
117+
results = new PeerEntry[](len);
118+
for (uint256 idx = 0; idx < len;) {
119+
results[idx].chain = chains[idx];
120+
results[idx].addr = getPeer(chains[idx]);
121+
unchecked {
122+
++idx;
123+
}
124+
}
125+
}
126+
127+
// =============== Admin ===============================================================
128+
129+
/// @inheritdoc ISharedWormholeTransceiver
130+
function updateAdmin(
131+
address newAdmin
132+
) external onlyAdmin {
133+
// SPEC: MUST check that the caller is the current admin and there is not a pending transfer.
134+
// - This is handled by onlyAdmin.
135+
136+
// SPEC: If possible, MUST NOT allow the admin to discard admin via this command (e.g. newAdmin != address(0) on EVM)
137+
if (newAdmin == address(0)) {
138+
revert InvalidAdminZeroAddress();
139+
}
140+
141+
// SPEC: Immediately sets newAdmin as the admin of the integrator.
142+
admin = newAdmin;
143+
emit AdminUpdated(msg.sender, newAdmin);
144+
}
145+
146+
/// @inheritdoc ISharedWormholeTransceiver
147+
function transferAdmin(
148+
address newAdmin
149+
) external onlyAdmin {
150+
// SPEC: MUST check that the caller is the current admin and there is not a pending transfer.
151+
// - This is handled by onlyAdmin.
152+
153+
// SPEC: If possible, MUST NOT allow the admin to discard admin via this command (e.g. `newAdmin != address(0)` on EVM).
154+
if (newAdmin == address(0)) {
155+
revert InvalidAdminZeroAddress();
156+
}
157+
158+
// SPEC: Initiates the first step of a two-step process in which the current admin (to cancel) or new admin must claim.
159+
pendingAdmin = newAdmin;
160+
emit AdminUpdateRequested(msg.sender, newAdmin);
161+
}
162+
163+
/// @inheritdoc ISharedWormholeTransceiver
164+
function claimAdmin() external {
165+
// This doesn't use onlyAdmin because the pending admin must be non-zero.
166+
167+
// SPEC: MUST check that the caller is the current admin OR the pending admin.
168+
if ((admin != msg.sender) && (pendingAdmin != msg.sender)) {
169+
revert CallerNotAdmin(msg.sender);
170+
}
171+
172+
// SPEC: MUST check that there is an admin transfer pending (e. g. pendingAdmin != address(0) on EVM).
173+
if (pendingAdmin == address(0)) {
174+
revert NoAdminUpdatePending();
175+
}
176+
177+
// SPEC: Cancels / Completes the second step of the two-step transfer. Sets the admin to the caller and clears the pending admin.
178+
address oldAdmin = admin;
179+
admin = msg.sender;
180+
pendingAdmin = address(0);
181+
emit AdminUpdated(oldAdmin, msg.sender);
182+
}
183+
184+
/// @inheritdoc ISharedWormholeTransceiver
185+
function discardAdmin() external onlyAdmin {
186+
// SPEC: MUST check that the caller is the current admin and there is not a pending transfer.
187+
// - This is handled by onlyAdmin.
188+
189+
// SPEC: Clears the current admin. THIS IS NOT REVERSIBLE. This ensures that the Integrator configuration becomes immutable.
190+
admin = address(0);
191+
emit AdminDiscarded(msg.sender);
192+
}
193+
194+
/// @inheritdoc ISharedWormholeTransceiver
195+
function setPeer(uint16 peerChain, bytes32 peerContract) external onlyAdmin {
196+
if (peerChain == 0 || peerChain == ourChain) {
197+
revert InvalidChain(peerChain);
198+
}
199+
if (peerContract == bytes32(0)) {
200+
revert InvalidPeerZeroAddress();
201+
}
202+
203+
bytes32 oldPeerContract = _getPeersStorage()[peerChain];
204+
205+
// SPEC: MUST not set the peer if it is already set.
206+
if (oldPeerContract != bytes32(0)) {
207+
revert PeerAlreadySet(peerChain, oldPeerContract);
208+
}
209+
210+
_getPeersStorage()[peerChain] = peerContract;
211+
_getChainsStorage().push(peerChain);
212+
emit PeerAdded(peerChain, peerContract);
213+
}
214+
215+
// =============== ITransceiver Interface ==============================================
216+
217+
/// @inheritdoc ITransceiver
218+
function getTransceiverType() external pure virtual returns (string memory) {
219+
return sharedWormholeTransceiverVersionString;
220+
}
221+
222+
/// @inheritdoc ITransceiver
223+
function quoteDeliveryPrice(
224+
uint16, // recipientChain
225+
TransceiverStructs.TransceiverInstruction calldata // instruction
226+
) external view virtual returns (uint256) {
227+
return wormhole.messageFee();
228+
}
229+
230+
/// @inheritdoc ITransceiver
231+
/// @dev The caller should set the delivery price in msg.value.
232+
/// @dev This transceiver does not use instructions, so that parameter is ignored.
233+
/// @dev This transceiver does not use refundAddress, so that parameter is ignored.
234+
function sendMessage(
235+
uint16 recipientChain,
236+
TransceiverStructs.TransceiverInstruction memory, // instruction,
237+
bytes memory nttManagerMessage,
238+
bytes32 recipientNttManagerAddress,
239+
bytes32 // refundAddress
240+
) external payable virtual {
241+
(
242+
TransceiverStructs.TransceiverMessage memory transceiverMessage,
243+
bytes memory encodedTransceiverPayload
244+
) = TransceiverStructs.buildAndEncodeTransceiverMessage(
245+
WH_TRANSCEIVER_PAYLOAD_PREFIX,
246+
toWormholeFormat(msg.sender),
247+
recipientNttManagerAddress,
248+
nttManagerMessage,
249+
new bytes(0)
250+
);
251+
252+
wormhole.publishMessage{value: msg.value}(0, encodedTransceiverPayload, consistencyLevel);
253+
emit SendTransceiverMessage(recipientChain, transceiverMessage);
254+
}
255+
256+
/// @inheritdoc ITransceiver
257+
/// @dev This transciever does not have a specific NttManager, so this function just reverts.
258+
function getNttManagerOwner() external pure returns (address) {
259+
revert NotImplemented();
260+
}
261+
262+
/// @inheritdoc ITransceiver
263+
/// @dev This transciever does not have a specific NttManager or token, so this function just reverts.
264+
function getNttManagerToken() external pure returns (address) {
265+
revert NotImplemented();
266+
}
267+
268+
/// @inheritdoc ITransceiver
269+
/// @dev Since shared transceivers are not owned by the manager, this function does nothing.
270+
/// We don't want to revert because a manager may have both shared and unshared transceivers.
271+
/// It should be able to call this without without worrying about the transceiver type.
272+
function transferTransceiverOwnership(
273+
address newOwner
274+
) external {}
275+
276+
/// @inheritdoc ITransceiver
277+
/// @dev Since shared transceivers are immutable, this just reverts.
278+
function upgrade(
279+
address // newImplementation
280+
) external pure {
281+
revert NotUpgradable();
282+
}
283+
284+
// =============== ISharedWormholeTransceiver Interface ================================
285+
286+
/// @inheritdoc ISharedWormholeTransceiver
287+
function receiveMessage(
288+
bytes calldata encodedMessage
289+
) external {
290+
// Verify the wormhole message and extract the source chain and payload.
291+
(uint16 sourceChainId, bytes memory payload) = _verifyMessage(encodedMessage);
292+
293+
// TODO: There is no check that this message is intended for this chain, and I don't see how to do it!
294+
295+
// Parse the encoded Transceiver payload and the encapsulated manager message.
296+
TransceiverStructs.TransceiverMessage memory parsedTransceiverMessage;
297+
TransceiverStructs.NttManagerMessage memory parsedNttManagerMessage;
298+
(parsedTransceiverMessage, parsedNttManagerMessage) = TransceiverStructs
299+
.parseTransceiverAndNttManagerMessage(WH_TRANSCEIVER_PAYLOAD_PREFIX, payload);
300+
301+
// We use recipientNttManagerAddress to deliver the message so it must be set.
302+
if (parsedTransceiverMessage.recipientNttManagerAddress == bytes32(0)) {
303+
revert RecipientManagerAddressIsZero();
304+
}
305+
306+
// Forward the message to the specified manager.
307+
IMsgReceiver(fromWormholeFormat(parsedTransceiverMessage.recipientNttManagerAddress))
308+
.attestationReceived(
309+
sourceChainId, parsedTransceiverMessage.sourceNttManagerAddress, parsedNttManagerMessage
310+
);
311+
312+
// We don't need to emit an event here because _verifyMessage already did.
313+
}
314+
315+
// ============= Internal ===============================================================
316+
317+
function _verifyMessage(
318+
bytes memory encodedMessage
319+
) internal returns (uint16, bytes memory) {
320+
// Verify VAA against Wormhole Core Bridge contract.
321+
(IWormhole.VM memory vm, bool valid, string memory reason) =
322+
wormhole.parseAndVerifyVM(encodedMessage);
323+
if (!valid) {
324+
revert InvalidVaa(reason);
325+
}
326+
327+
// Ensure that the message came from the registered peer contract.
328+
if (getPeer(vm.emitterChainId) != vm.emitterAddress) {
329+
revert InvalidPeer(vm.emitterChainId, vm.emitterAddress);
330+
}
331+
332+
emit ReceivedMessage(vm.hash, vm.emitterChainId, vm.emitterAddress, vm.sequence);
333+
return (vm.emitterChainId, vm.payload);
334+
}
335+
336+
// =============== MODIFIERS ===============================================
337+
338+
modifier onlyAdmin() {
339+
if (admin != msg.sender) {
340+
revert CallerNotAdmin(msg.sender);
341+
}
342+
if (pendingAdmin != address(0)) {
343+
revert AdminTransferPending();
344+
}
345+
_;
346+
}
347+
}

0 commit comments

Comments
 (0)