Skip to content

Commit

Permalink
v1.1 example update
Browse files Browse the repository at this point in the history
  • Loading branch information
derpy-duck committed Nov 3, 2023
1 parent a11e814 commit 231c267
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 52 deletions.
28 changes: 4 additions & 24 deletions beyond-hello-wormhole.md
Original file line number Diff line number Diff line change
Expand Up @@ -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!

Expand Down Expand Up @@ -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(
Expand All @@ -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));
Expand Down
2 changes: 1 addition & 1 deletion src/HelloWormhole.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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");

Expand Down
73 changes: 54 additions & 19 deletions src/extensions/HelloWormholeConfirmation.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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}(
Expand All @@ -38,49 +59,63 @@ 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(
bytes memory payload,
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;
}
}
}
}
29 changes: 22 additions & 7 deletions src/extensions/HelloWormholeProtections.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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}(
Expand All @@ -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);
Expand Down

0 comments on commit 231c267

Please sign in to comment.