From 231c26771f68bbbb092226cadce1532b4f552a1c Mon Sep 17 00:00:00 2001 From: derpy-duck <115193320+derpy-duck@users.noreply.github.com> Date: Fri, 3 Nov 2023 10:38:22 -0400 Subject: [PATCH] v1.1 example update --- beyond-hello-wormhole.md | 28 ++------ lib/wormhole-solidity-sdk | 2 +- src/HelloWormhole.sol | 2 +- src/extensions/HelloWormholeConfirmation.sol | 73 +++++++++++++++----- src/extensions/HelloWormholeProtections.sol | 29 ++++++-- 5 files changed, 82 insertions(+), 52 deletions(-) diff --git a/beyond-hello-wormhole.md b/beyond-hello-wormhole.md index 9cd9bb4..e78fa2b 100644 --- a/beyond-hello-wormhole.md +++ b/beyond-hello-wormhole.md @@ -8,16 +8,14 @@ HelloWormhole is a great example application, but has much room for improvement. Topics covered: -- Protections - - Restricting the sender - - Preventing duplicate deliveries +- Restricting the sender - Refunds - Chained Deliveries - Delivering existing VAAs ## Protections -### Problem 1 - The greetings can come from anyone +### Issue: The greetings can come from anyone A user doesn’t have to go through the HelloWormhole contract to request a greeting - they can call `wormholeRelayer.sendPayloadToEvm{value: cost}(…)` themselves! @@ -49,26 +47,9 @@ Often, it is desirable that all of the requests go through your own source contr } ``` -### Problem 2 - The greetings can be relayed multiple times +### Example Solution for Problem 1 -As mentioned in the first article, without having the mapping of delivery hashes to boolean, anyone can fetch the delivery VAA corresponding to a sent greeting, and have it delivered again to the target HelloWormhole contract! This causes another `GreetingReceived` event to be emitted from the same `senderChain` and `sender`, even though the sender only intended on sending this greeting once. - -**Solution:** In our implementation of receiveWormholeMessages, we store each delivery hash in a mapping from delivery hashes to booleans, to indicate that the delivery has already been processed. Then, at the beginning we can check to see if the delivery has already been processed, and revert if it has. - -```solidity - - mapping(bytes32 => bool) public seenDeliveryVaaHashes; - - modifier replayProtect(bytes32 deliveryHash) { - require(!seenDeliveryVaaHashes[deliveryHash], "Message already processed"); - seenDeliveryVaaHashes[deliveryHash] = true; - _; - } -``` - -### Example Solution for Problems 1 and 2 - -We provide a base class in the [Wormhole Solidity SDK](https://github.com/wormhole-foundation/wormhole-solidity-sdk) that includes the modifiers shown above, makes it easy to add these functionalities as such +We provide a base class in the [Wormhole Solidity SDK](https://github.com/wormhole-foundation/wormhole-solidity-sdk) that includes the modifier shown above, makes it easy to add these functionalities as such ```solidity function receiveWormholeMessages( @@ -83,7 +64,6 @@ We provide a base class in the [Wormhole Solidity SDK](https://github.com/wormho override onlyWormholeRelayer isRegisteredSender(sourceChain, sourceAddress) - replayProtect(deliveryHash) { latestGreeting = abi.decode(payload, (string)); diff --git a/lib/wormhole-solidity-sdk b/lib/wormhole-solidity-sdk index 456f69f..bacbe82 160000 --- a/lib/wormhole-solidity-sdk +++ b/lib/wormhole-solidity-sdk @@ -1 +1 @@ -Subproject commit 456f69f1912fac93f21845f71df42128d6b755f6 +Subproject commit bacbe82e6ae3f7f5ec7cdcd7d480f1e528471bbb diff --git a/src/HelloWormhole.sol b/src/HelloWormhole.sol index 9d1bcc9..f59b9f0 100644 --- a/src/HelloWormhole.sol +++ b/src/HelloWormhole.sol @@ -48,7 +48,7 @@ contract HelloWormhole is IWormholeReceiver { bytes[] memory, // additionalVaas bytes32, // address that called 'sendPayloadToEvm' (HelloWormhole contract address) uint16 sourceChain, - bytes32 deliveryHash // this can be stored in a mapping deliveryHash => bool to prevent duplicate deliveries + bytes32 // unique identifier of delivery ) public payable override { require(msg.sender == address(wormholeRelayer), "Only relayer allowed"); diff --git a/src/extensions/HelloWormholeConfirmation.sol b/src/extensions/HelloWormholeConfirmation.sol index 5cd9918..61d67a2 100644 --- a/src/extensions/HelloWormholeConfirmation.sol +++ b/src/extensions/HelloWormholeConfirmation.sol @@ -17,18 +17,39 @@ contract HelloWormholeConfirmation is Base, IWormholeReceiver { uint16 chainId; - enum MessageType {GREETING, CONFIRMATION} + enum MessageType { + GREETING, + CONFIRMATION + } - constructor(address _wormholeRelayer, address _wormhole) Base(_wormholeRelayer, _wormhole) {} + constructor( + address _wormholeRelayer, + address _wormhole + ) Base(_wormholeRelayer, _wormhole) {} - function quoteCrossChainGreeting(uint16 targetChain, uint256 receiverValue) public view returns (uint256 cost) { - (cost,) = wormholeRelayer.quoteEVMDeliveryPrice(targetChain, receiverValue, SENDING_GAS_LIMIT); + function quoteCrossChainGreeting( + uint16 targetChain, + uint256 receiverValue + ) public view returns (uint256 cost) { + (cost, ) = wormholeRelayer.quoteEVMDeliveryPrice( + targetChain, + receiverValue, + SENDING_GAS_LIMIT + ); } // receiverValueForSecondDeliveryPayment will be determined in a front-end calculation (by calling quoteConfirmation on the target chain) // We recommend baking in a buffer to account for the possibility of the price of targetChain->sourceChain changing during the sourceChain->targetChain delivery - function sendCrossChainGreeting(uint16 targetChain, address targetAddress, string memory greeting, uint256 receiverValueForSecondDeliveryPayment) public payable { - uint256 cost = quoteCrossChainGreeting(targetChain, receiverValueForSecondDeliveryPayment); + function sendCrossChainGreeting( + uint16 targetChain, + address targetAddress, + string memory greeting, + uint256 receiverValueForSecondDeliveryPayment + ) public payable { + uint256 cost = quoteCrossChainGreeting( + targetChain, + receiverValueForSecondDeliveryPayment + ); require(msg.value == cost); wormholeRelayer.sendPayloadToEvm{value: cost}( @@ -38,13 +59,19 @@ contract HelloWormholeConfirmation is Base, IWormholeReceiver { receiverValueForSecondDeliveryPayment, // will be used to pay for the confirmation SENDING_GAS_LIMIT, // we add a refund chain and address as the requester of the cross chain greeting - chainId, + chainId, msg.sender ); } - function quoteConfirmation(uint16 targetChain) public view returns (uint256 cost) { - (cost,) = wormholeRelayer.quoteEVMDeliveryPrice(targetChain, 0, CONFIRMATION_GAS_LIMIT); + function quoteConfirmation( + uint16 targetChain + ) public view returns (uint256 cost) { + (cost, ) = wormholeRelayer.quoteEVMDeliveryPrice( + targetChain, + 0, + CONFIRMATION_GAS_LIMIT + ); } function receiveWormholeMessages( @@ -52,35 +79,43 @@ contract HelloWormholeConfirmation is Base, IWormholeReceiver { bytes[] memory, // additionalVaas bytes32 sourceAddress, uint16 sourceChain, - bytes32 deliveryHash + bytes32 // deliveryHash ) public payable override onlyWormholeRelayer isRegisteredSender(sourceChain, sourceAddress) - replayProtect(deliveryHash) { MessageType msgType = abi.decode(payload, (MessageType)); - if(msgType == MessageType.GREETING) { - (,string memory greeting, address sender) = abi.decode(payload, (MessageType, string, address)); + if (msgType == MessageType.GREETING) { + (, string memory greeting, address sender) = abi.decode( + payload, + (MessageType, string, address) + ); latestGreeting = greeting; emit GreetingReceived(latestGreeting, sourceChain, sender); uint256 confirmationCost = quoteConfirmation(sourceChain); - require(msg.value >= confirmationCost, "Didn't receive enough value for the second send!"); + require( + msg.value >= confirmationCost, + "Didn't receive enough value for the second send!" + ); wormholeRelayer.sendPayloadToEvm{value: confirmationCost}( - sourceChain, - fromWormholeFormat(sourceAddress), + sourceChain, + fromWormholeFormat(sourceAddress), abi.encode(MessageType.CONFIRMATION, greeting, sender), 0, CONFIRMATION_GAS_LIMIT ); - } else if(msgType == MessageType.CONFIRMATION) { - (,string memory greeting, address sender) = abi.decode(payload, (MessageType, string, address)); + } else if (msgType == MessageType.CONFIRMATION) { + (, string memory greeting, address sender) = abi.decode( + payload, + (MessageType, string, address) + ); emit GreetingSuccess(greeting, sender); latestConfirmedSentGreeting = greeting; } } -} \ No newline at end of file +} diff --git a/src/extensions/HelloWormholeProtections.sol b/src/extensions/HelloWormholeProtections.sol index 2755aa2..863c7ee 100644 --- a/src/extensions/HelloWormholeProtections.sol +++ b/src/extensions/HelloWormholeProtections.sol @@ -12,13 +12,26 @@ contract HelloWormholeProtections is Base, IWormholeReceiver { string public latestGreeting; - constructor(address _wormholeRelayer, address _wormhole) Base(_wormholeRelayer, _wormhole) {} + constructor( + address _wormholeRelayer, + address _wormhole + ) Base(_wormholeRelayer, _wormhole) {} - function quoteCrossChainGreeting(uint16 targetChain) public view returns (uint256 cost) { - (cost,) = wormholeRelayer.quoteEVMDeliveryPrice(targetChain, 0, GAS_LIMIT); + function quoteCrossChainGreeting( + uint16 targetChain + ) public view returns (uint256 cost) { + (cost, ) = wormholeRelayer.quoteEVMDeliveryPrice( + targetChain, + 0, + GAS_LIMIT + ); } - function sendCrossChainGreeting(uint16 targetChain, address targetAddress, string memory greeting) public payable { + function sendCrossChainGreeting( + uint16 targetChain, + address targetAddress, + string memory greeting + ) public payable { uint256 cost = quoteCrossChainGreeting(targetChain); require(msg.value == cost); wormholeRelayer.sendPayloadToEvm{value: cost}( @@ -35,16 +48,18 @@ contract HelloWormholeProtections is Base, IWormholeReceiver { bytes[] memory, // additionalVaas bytes32 sourceAddress, uint16 sourceChain, - bytes32 deliveryHash + bytes32 // delivery hash ) public payable override onlyWormholeRelayer isRegisteredSender(sourceChain, sourceAddress) - replayProtect(deliveryHash) { - (string memory greeting, address sender) = abi.decode(payload, (string, address)); + (string memory greeting, address sender) = abi.decode( + payload, + (string, address) + ); latestGreeting = greeting; emit GreetingReceived(latestGreeting, sourceChain, sender);