Skip to content

Commit

Permalink
Hello Wormhole Confirmation
Browse files Browse the repository at this point in the history
  • Loading branch information
derpy-duck committed Jul 14, 2023
1 parent b0311c7 commit 83c0f99
Show file tree
Hide file tree
Showing 2 changed files with 146 additions and 0 deletions.
88 changes: 88 additions & 0 deletions src/extensions/HelloWormholeConfirmation.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import "wormhole-solidity-sdk/interfaces/IWormholeRelayer.sol";
import "wormhole-solidity-sdk/interfaces/IWormholeReceiver.sol";
import "wormhole-solidity-sdk/WormholeRelayerSDK.sol";

contract HelloWormholeConfirmation is Base, IWormholeReceiver {
event GreetingReceived(string greeting, uint16 senderChain, address sender);
event GreetingSuccess(string greeting, address sender);

uint256 constant SENDING_GAS_LIMIT = 550_000;
uint256 constant CONFIRMATION_GAS_LIMIT = 50_000;

string public latestGreeting;
string public latestConfirmedSentGreeting;

uint16 chainId;

enum MessageType {GREETING, CONFIRMATION}

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);
}

// 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);
require(msg.value == cost);

wormholeRelayer.sendPayloadToEvm{value: cost}(
targetChain,
targetAddress,
abi.encode(MessageType.GREETING, greeting, msg.sender), // payload
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
// The refund from this 'send' will be tacked on to the confirmation delivery
// (because we will request the confirmation using the 'forward' feature)
chainId,
msg.sender
);
}

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
)
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));
latestGreeting = greeting;
emit GreetingReceived(latestGreeting, sourceChain, sender);

uint256 confirmationCost = quoteConfirmation(sourceChain);
require(msg.value >= confirmationCost, "Didn't receive enough value for the forward!");
wormholeRelayer.forwardPayloadToEvm{value: msg.value}(
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));
emit GreetingSuccess(greeting, sender);
latestConfirmedSentGreeting = greeting;
}
}
}
58 changes: 58 additions & 0 deletions test/extensions/HelloWormholeConfirmation.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import "../../src/extensions/HelloWormholeConfirmation.sol";

import "wormhole-solidity-sdk/testing/WormholeRelayerTest.sol";

contract HelloWormholeConfirmationTest is WormholeRelayerBasicTest {
event GreetingReceived(string greeting, uint16 senderChain, address sender);

HelloWormholeConfirmation helloSource;
HelloWormholeConfirmation helloTarget;

function setUpSource() public override {
helloSource = new HelloWormholeConfirmation(address(relayerSource), address(wormholeSource));
}

function setUpTarget() public override {
helloTarget = new HelloWormholeConfirmation(address(relayerTarget), address(wormholeTarget));
}

function performRegistrations() public {
vm.selectFork(targetFork);
helloTarget.setRegisteredSender(sourceChain, toWormholeFormat(address(helloSource)));

vm.selectFork(sourceFork);
helloSource.setRegisteredSender(targetChain, toWormholeFormat(address(helloTarget)));
}

function testGreeting() public {

performRegistrations();

// Front-end calculation for how much receiver value to request the greeting with
// to ensure a confirmation is able to come back!
vm.selectFork(targetFork);
// We bake in a 10% buffer to account for the possibility of a price change after the initial delivery but before the return delivery
uint256 receiverValueForConfirmation = helloTarget.quoteConfirmation(sourceChain) * 11 / 10;
vm.selectFork(sourceFork);
// end front-end calculation

uint256 cost = helloSource.quoteCrossChainGreeting(targetChain, receiverValueForConfirmation);

vm.recordLogs();

helloSource.sendCrossChainGreeting{value: cost}(targetChain, address(helloTarget), "Hello Wormhole!", receiverValueForConfirmation);

performDelivery();

vm.selectFork(targetFork);
assertEq(helloTarget.latestGreeting(), "Hello Wormhole!");

performDelivery();

vm.selectFork(sourceFork);
assertEq(helloSource.latestConfirmedSentGreeting(), "Hello Wormhole!");
}
}

0 comments on commit 83c0f99

Please sign in to comment.