Skip to content

Commit e00fcd7

Browse files
authored
evm: Wormhole endpoint registration (#161)
1 parent bda4124 commit e00fcd7

9 files changed

+202
-36
lines changed

evm/src/Endpoint.sol

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ abstract contract Endpoint is
3636

3737
/// =============== ADMIN ===============================================
3838

39-
function _initialize() internal override {
39+
function _initialize() internal virtual override {
4040
__ReentrancyGuard_init();
4141
// owner of the endpoint is set to the owner of the manager
4242
__PausedOwnable_init(msg.sender, getManagerOwner());

evm/src/Manager.sol

+6-2
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,10 @@ contract Manager is
127127
emit ThresholdChanged(oldThreshold, threshold);
128128
}
129129

130+
function getMode() public view returns (uint8) {
131+
return uint8(mode);
132+
}
133+
130134
/// @notice Returns the number of Endpoints that must attest to a msgId for
131135
/// it to be considered valid and acted upon.
132136
function getThreshold() public view returns (uint8) {
@@ -759,8 +763,8 @@ contract Manager is
759763
return countSetBits(_getMessageAttestations(digest));
760764
}
761765

762-
function _tokenDecimals() internal view override returns (uint8) {
763-
return tokenDecimals;
766+
function tokenDecimals() public view override(IManager, RateLimiter) returns (uint8) {
767+
return tokenDecimals_;
764768
}
765769

766770
/// ============== INVARIANTS =============================================

evm/src/NttNormalizer.sol

+4-4
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ abstract contract NttNormalizer {
77
using NormalizedAmountLib for uint256;
88
using NormalizedAmountLib for NormalizedAmount;
99

10-
uint8 immutable tokenDecimals;
10+
uint8 internal immutable tokenDecimals_;
1111

1212
constructor(address _token) {
13-
tokenDecimals = _tokenDecimals(_token);
13+
tokenDecimals_ = _tokenDecimals(_token);
1414
}
1515

1616
function _tokenDecimals(address token) internal view returns (uint8) {
@@ -19,11 +19,11 @@ abstract contract NttNormalizer {
1919
}
2020

2121
function _nttNormalize(uint256 amount) internal view returns (NormalizedAmount memory) {
22-
return amount.normalize(tokenDecimals);
22+
return amount.normalize(tokenDecimals_);
2323
}
2424

2525
function _nttDenormalize(NormalizedAmount memory amount) internal view returns (uint256) {
26-
return amount.denormalize(tokenDecimals);
26+
return amount.denormalize(tokenDecimals_);
2727
}
2828

2929
/// @dev Shift decimals of `amount` to match the token decimals

evm/src/WormholeEndpoint.sol

+45-1
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ import "wormhole-solidity-sdk/libraries/BytesParsing.sol";
66
import "wormhole-solidity-sdk/interfaces/IWormhole.sol";
77

88
import "./libraries/EndpointHelpers.sol";
9+
import "./libraries/EndpointStructs.sol";
910
import "./interfaces/IWormholeEndpoint.sol";
1011
import "./interfaces/ISpecialRelayer.sol";
12+
import "./interfaces/IManager.sol";
1113
import "./Endpoint.sol";
1214

1315
contract WormholeEndpoint is Endpoint, IWormholeEndpoint, IWormholeReceiver {
@@ -22,6 +24,14 @@ contract WormholeEndpoint is Endpoint, IWormholeEndpoint, IWormholeReceiver {
2224
/// Note that this is not a security critical field. It's meant to be used by messaging providers to identify which messages are Endpoint-related.
2325
bytes4 constant WH_ENDPOINT_PAYLOAD_PREFIX = 0x9945FF10;
2426

27+
/// @dev Prefix for all Wormhole endpoint initialisation payloads
28+
/// This is bytes4(keccak256("WormholeEndpointInit"))
29+
bytes4 constant WH_ENDPOINT_INIT_PREFIX = 0xc83e3d2e;
30+
31+
/// @dev Prefix for all Wormhole sibling registration payloads
32+
/// This is bytes4(keccak256("WormholeSiblingRegistration"))
33+
bytes4 constant WH_SIBLING_REGISTRATION_PREFIX = 0xd0d292f1;
34+
2535
IWormhole public immutable wormhole;
2636
IWormholeRelayer public immutable wormholeRelayer;
2737
ISpecialRelayer public immutable specialRelayer;
@@ -126,6 +136,22 @@ contract WormholeEndpoint is Endpoint, IWormholeEndpoint, IWormholeReceiver {
126136
consistencyLevel = _consistencyLevel;
127137
}
128138

139+
function _initialize() internal override {
140+
super._initialize();
141+
_initializeEndpoint();
142+
}
143+
144+
function _initializeEndpoint() internal {
145+
EndpointStructs.EndpointInit memory init = EndpointStructs.EndpointInit({
146+
endpointIdentifier: WH_ENDPOINT_INIT_PREFIX,
147+
managerAddress: toWormholeFormat(manager),
148+
managerMode: IManager(manager).getMode(),
149+
tokenAddress: toWormholeFormat(managerToken),
150+
tokenDecimals: IManager(manager).tokenDecimals()
151+
});
152+
wormhole.publishMessage(0, EndpointStructs.encodeEndpointInit(init), consistencyLevel);
153+
}
154+
129155
function _checkInvalidRelayingConfig(uint16 chainId) internal view returns (bool) {
130156
return isWormholeRelayingEnabled(chainId) && !isWormholeEvmChain(chainId);
131157
}
@@ -328,9 +354,27 @@ contract WormholeEndpoint is Endpoint, IWormholeEndpoint, IWormholeReceiver {
328354

329355
bytes32 oldSiblingContract = _getWormholeSiblingsStorage()[chainId];
330356

357+
// We don't want to allow updating a sibling since this adds complexity in the accountant
358+
// If the owner makes a mistake with sibling registration they should deploy a new Wormhole
359+
// endpoint and register this new endpoint with the Manager
360+
if (oldSiblingContract != bytes32(0)) {
361+
revert SiblingAlreadySet(chainId, oldSiblingContract);
362+
}
363+
331364
_getWormholeSiblingsStorage()[chainId] = siblingContract;
332365

333-
emit SetWormholeSibling(chainId, oldSiblingContract, siblingContract);
366+
// Publish a message for this endpoint registration
367+
EndpointStructs.EndpointRegistration memory registration = EndpointStructs
368+
.EndpointRegistration({
369+
endpointIdentifier: WH_SIBLING_REGISTRATION_PREFIX,
370+
endpointChainId: chainId,
371+
endpointAddress: siblingContract
372+
});
373+
wormhole.publishMessage(
374+
0, EndpointStructs.encodeEndpointRegistration(registration), consistencyLevel
375+
);
376+
377+
emit SetWormholeSibling(chainId, siblingContract);
334378
}
335379

336380
function isWormholeRelayingEnabled(uint16 chainId) public view returns (bool) {

evm/src/interfaces/IManager.sol

+8
Original file line numberDiff line numberDiff line change
@@ -133,4 +133,12 @@ interface IManager {
133133
///
134134
/// @param newImplementation The address of the new implementation.
135135
function upgrade(address newImplementation) external;
136+
137+
/// @notice Returns the mode (locking or burning) of the Manager.
138+
/// @return mode A uint8 corresponding to the mode
139+
function getMode() external view returns (uint8);
140+
141+
/// @notice Returns the number of decimals of the token managed by the Manager.
142+
/// @return decimals The number of decimals of the token.
143+
function tokenDecimals() external view returns (uint8);
136144
}

evm/src/interfaces/IWormholeEndpoint.sol

+2-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ interface IWormholeEndpoint {
1010
);
1111

1212
event SendEndpointMessage(uint16 recipientChain, EndpointStructs.EndpointMessage message);
13-
event SetWormholeSibling(uint16 chainId, bytes32 oldSiblingContract, bytes32 siblingContract);
13+
event SetWormholeSibling(uint16 chainId, bytes32 siblingContract);
1414
event SetIsWormholeRelayingEnabled(uint16 chainId, bool isRelayingEnabled);
1515
event SetIsSpecialRelayingEnabled(uint16 chainId, bool isRelayingEnabled);
1616
event SetIsWormholeEvmChain(uint16 chainId);
@@ -20,6 +20,7 @@ interface IWormholeEndpoint {
2020
error UnexpectedAdditionalMessages();
2121
error InvalidVaa(string reason);
2222
error InvalidWormholeSibling(uint16 chainId, bytes32 siblingAddress);
23+
error SiblingAlreadySet(uint16 chainId, bytes32 siblingAddress);
2324
error TransferAlreadyCompleted(bytes32 vaaHash);
2425
error InvalidWormholeSiblingZeroAddress();
2526
error InvalidWormholeChainIdZero();

evm/src/libraries/EndpointStructs.sol

+62
Original file line numberDiff line numberDiff line change
@@ -351,4 +351,66 @@ library EndpointStructs {
351351

352352
return instructions;
353353
}
354+
355+
struct EndpointInit {
356+
bytes4 endpointIdentifier;
357+
bytes32 managerAddress;
358+
uint8 managerMode;
359+
bytes32 tokenAddress;
360+
uint8 tokenDecimals;
361+
}
362+
363+
function encodeEndpointInit(EndpointInit memory init) public pure returns (bytes memory) {
364+
return abi.encodePacked(
365+
init.endpointIdentifier,
366+
init.managerAddress,
367+
init.managerMode,
368+
init.tokenAddress,
369+
init.tokenDecimals
370+
);
371+
}
372+
373+
function decodeEndpointInit(bytes memory encoded)
374+
public
375+
pure
376+
returns (EndpointInit memory init)
377+
{
378+
uint256 offset = 0;
379+
(init.endpointIdentifier, offset) = encoded.asBytes4Unchecked(offset);
380+
(init.managerAddress, offset) = encoded.asBytes32Unchecked(offset);
381+
(init.managerMode, offset) = encoded.asUint8Unchecked(offset);
382+
(init.tokenAddress, offset) = encoded.asBytes32Unchecked(offset);
383+
(init.tokenDecimals, offset) = encoded.asUint8Unchecked(offset);
384+
encoded.checkLength(offset);
385+
}
386+
387+
struct EndpointRegistration {
388+
bytes4 endpointIdentifier;
389+
uint16 endpointChainId;
390+
bytes32 endpointAddress;
391+
}
392+
393+
function encodeEndpointRegistration(EndpointRegistration memory registration)
394+
public
395+
pure
396+
returns (bytes memory)
397+
{
398+
return abi.encodePacked(
399+
registration.endpointIdentifier,
400+
registration.endpointChainId,
401+
registration.endpointAddress
402+
);
403+
}
404+
405+
function decodeEndpointRegistration(bytes memory encoded)
406+
public
407+
pure
408+
returns (EndpointRegistration memory registration)
409+
{
410+
uint256 offset = 0;
411+
(registration.endpointIdentifier, offset) = encoded.asBytes4Unchecked(offset);
412+
(registration.endpointChainId, offset) = encoded.asUint16Unchecked(offset);
413+
(registration.endpointAddress, offset) = encoded.asBytes32Unchecked(offset);
414+
encoded.checkLength(offset);
415+
}
354416
}

evm/src/libraries/RateLimiter.sol

+3-3
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ abstract contract RateLimiter is IRateLimiter, IRateLimiterEvents {
9797

9898
function getCurrentOutboundCapacity() public view returns (uint256) {
9999
NormalizedAmount memory normalizedCapacity = _getCurrentCapacity(_getOutboundLimitParams());
100-
uint8 decimals = _tokenDecimals();
100+
uint8 decimals = tokenDecimals();
101101
return normalizedCapacity.denormalize(decimals);
102102
}
103103

@@ -124,7 +124,7 @@ abstract contract RateLimiter is IRateLimiter, IRateLimiterEvents {
124124
function getCurrentInboundCapacity(uint16 chainId_) public view returns (uint256) {
125125
NormalizedAmount memory normalizedCapacity =
126126
_getCurrentCapacity(_getInboundLimitParams(chainId_));
127-
uint8 decimals = _tokenDecimals();
127+
uint8 decimals = tokenDecimals();
128128
return normalizedCapacity.denormalize(decimals);
129129
}
130130

@@ -300,5 +300,5 @@ abstract contract RateLimiter is IRateLimiter, IRateLimiterEvents {
300300
emit InboundTransferQueued(digest);
301301
}
302302

303-
function _tokenDecimals() internal view virtual returns (uint8);
303+
function tokenDecimals() public view virtual returns (uint8);
304304
}

evm/test/IntegrationRelayer.t.sol

+71-24
Original file line numberDiff line numberDiff line change
@@ -483,16 +483,17 @@ contract TestRelayerEndToEndManual is
483483
// Register sibling contracts for the manager and endpoint. Endpoints and manager each have the concept of siblings here.
484484
managerChain1.setSibling(chainId2, bytes32(uint256(uint160(address(managerChain2)))));
485485
managerChain2.setSibling(chainId1, bytes32(uint256(uint160(address(managerChain1)))));
486+
}
486487

488+
function test_relayerEndpointAuth() public {
489+
// Set up sensible WH endpoint siblings
487490
wormholeEndpointChain1.setWormholeSibling(
488491
chainId2, bytes32(uint256(uint160((address(wormholeEndpointChain2)))))
489492
);
490493
wormholeEndpointChain2.setWormholeSibling(
491494
chainId1, bytes32(uint256(uint160(address(wormholeEndpointChain1))))
492495
);
493-
}
494496

495-
function test_relayerEndpointAuth() public {
496497
vm.recordLogs();
497498
vm.chainId(chainId1);
498499

@@ -534,30 +535,8 @@ contract TestRelayerEndToEndManual is
534535
vm.stopPrank();
535536
vm.chainId(chainId2);
536537

537-
// Caller is not proper who to receive messages from
538538
bytes[] memory a;
539-
wormholeEndpointChain2.setWormholeSibling(chainId1, bytes32(uint256(uint160(address(0x1)))));
540-
vm.startPrank(relayer);
541-
vm.expectRevert(
542-
abi.encodeWithSelector(
543-
IWormholeEndpoint.InvalidWormholeSibling.selector,
544-
chainId1,
545-
address(wormholeEndpointChain1)
546-
)
547-
);
548-
wormholeEndpointChain2.receiveWormholeMessages(
549-
vaa.payload,
550-
a,
551-
bytes32(uint256(uint160(address(wormholeEndpointChain1)))),
552-
vaa.emitterChainId,
553-
vaa.hash
554-
);
555-
vm.stopPrank();
556539

557-
// Bad manager sibling calling
558-
wormholeEndpointChain2.setWormholeSibling(
559-
chainId1, bytes32(uint256(uint160(address(wormholeEndpointChain1))))
560-
);
561540
managerChain2.setSibling(chainId1, bytes32(uint256(uint160(address(0x1)))));
562541
vm.startPrank(relayer);
563542
vm.expectRevert(); // bad manager sibling
@@ -624,4 +603,72 @@ contract TestRelayerEndToEndManual is
624603
vaa.hash // Hash of the VAA being used
625604
);
626605
}
606+
607+
function test_relayerWithInvalidWHEndpoint() public {
608+
// Set up dodgy wormhole endpoint siblings
609+
wormholeEndpointChain2.setWormholeSibling(chainId1, bytes32(uint256(uint160(address(0x1)))));
610+
wormholeEndpointChain1.setWormholeSibling(
611+
chainId2, bytes32(uint256(uint160(address(wormholeEndpointChain2))))
612+
);
613+
614+
vm.recordLogs();
615+
vm.chainId(chainId1);
616+
617+
// Setting up the transfer
618+
DummyToken token1 = DummyToken(managerChain1.token());
619+
620+
uint8 decimals = token1.decimals();
621+
uint256 sendingAmount = 5 * 10 ** decimals;
622+
token1.mintDummy(address(userA), 5 * 10 ** decimals);
623+
vm.startPrank(userA);
624+
token1.approve(address(managerChain1), sendingAmount);
625+
626+
// Send token through the relayer
627+
{
628+
vm.deal(userA, 1 ether);
629+
managerChain1.transfer{
630+
value: wormholeEndpointChain1.quoteDeliveryPrice(
631+
chainId2, buildEndpointInstruction(false)
632+
)
633+
}(
634+
sendingAmount,
635+
chainId2,
636+
bytes32(uint256(uint160(userB))),
637+
false,
638+
encodeEndpointInstruction(false)
639+
);
640+
}
641+
642+
// Get the messages from the logs for the sender
643+
vm.chainId(chainId2);
644+
Vm.Log[] memory entries = guardian.fetchWormholeMessageFromLog(vm.getRecordedLogs());
645+
bytes[] memory encodedVMs = new bytes[](entries.length);
646+
for (uint256 i = 0; i < encodedVMs.length; i++) {
647+
encodedVMs[i] = guardian.fetchSignedMessageFromLogs(entries[i], chainId1);
648+
}
649+
650+
IWormhole.VM memory vaa = wormhole.parseVM(encodedVMs[0]);
651+
652+
vm.stopPrank();
653+
vm.chainId(chainId2);
654+
655+
// Caller is not proper who to receive messages from
656+
bytes[] memory a;
657+
vm.startPrank(relayer);
658+
vm.expectRevert(
659+
abi.encodeWithSelector(
660+
IWormholeEndpoint.InvalidWormholeSibling.selector,
661+
chainId1,
662+
address(wormholeEndpointChain1)
663+
)
664+
);
665+
wormholeEndpointChain2.receiveWormholeMessages(
666+
vaa.payload,
667+
a,
668+
bytes32(uint256(uint160(address(wormholeEndpointChain1)))),
669+
vaa.emitterChainId,
670+
vaa.hash
671+
);
672+
vm.stopPrank();
673+
}
627674
}

0 commit comments

Comments
 (0)