diff --git a/README.md b/README.md index c1ac0e6..350abab 100644 --- a/README.md +++ b/README.md @@ -233,12 +233,12 @@ interface IWormholeReceiver { * on every call, checks that deliveryHash has not already been stored in the * map (This is to prevent other users maliciously trying to relay the same message) * - Checks that `sourceChain` and `sourceAddress` are indeed who - * you expect to have requested the calling of `send` or `forward` on the source chain + * you expect to have requested the calling of `send` on the source chain * * The invocation of this function corresponding to the `send` request will have msg.value equal * to the receiverValue specified in the send request. * - * If the invocation of this function reverts or exceeds the gas limit + * If the invocation of this function reverts or exceeds the gas limit * specified by the send requester, this delivery will result in a `ReceiverFailure`. * * @param payload - an arbitrary message which was included in the delivery by the diff --git a/beyond-hello-wormhole.md b/beyond-hello-wormhole.md index fbfa44c..9cd9bb4 100644 --- a/beyond-hello-wormhole.md +++ b/beyond-hello-wormhole.md @@ -12,7 +12,7 @@ Topics covered: - Restricting the sender - Preventing duplicate deliveries - Refunds -- Forwarding +- Chained Deliveries - Delivering existing VAAs ## Protections @@ -168,37 +168,6 @@ How do you know how much receiver value to request in your delivery on chain A? Included in the HelloWormhole repository is an [example contract](https://github.com/wormhole-foundation/hello-wormhole/blob/main/src/extensions/HelloWormholeConfirmation.sol) (and [forge tests](https://github.com/wormhole-foundation/hello-wormhole/blob/main/test/extensions/HelloWormholeConfirmation.t.sol)) that go from chain A to chain B to chain C, using the recommendation above. -There is still a downside - if you had provided a `refundAddress`, you are likely entitled to some amount of chain B currency as a refund from your chain A → B delivery! Ideally, you’d like to use this refund as part of the funding towards executing your B → C delivery request. If that isn’t possible, your next best options are to provide a wallet on the target chain to receive your refund, or request your refund be sent to a different chain (in which case you lose a portion of your refund to the fee of an additional delivery). - -We provide an [alternative way to achieve this that provides some cost savings: Forwarding](https://github.com/wormhole-foundation/wormhole/blob/main/ethereum/contracts/interfaces/relayer/IWormholeRelayer.sol#L271). The purpose of forwarding is to use the refund from chain A → B to add to the funding of the delivery from chain B → C. - -Included in the HelloWormhole repository is an [example contract](https://github.com/wormhole-foundation/hello-wormhole/blob/main/src/extensions/HelloWormholeForwarding.sol) (and [forge tests](https://github.com/wormhole-foundation/hello-wormhole/blob/main/test/extensions/HelloWormholeForwarding.t.sol)) that use the forwarding feature as described, along with the 'front-end' recommendation described above. - -Simply use `forwardPayloadToEvm` instead of `sendPayloadToEvm` to use this functionality! - -```solidity -/* - * The following equation must be satisfied - * (sum_f indicates summing over all forwards requested in - * `receiveWormholeMessages`): - * (refund amount from current execution of receiveWormholeMessages) - * + sum_f [msg.value_f] - * >= sum_f [quoteEVMDeliveryPrice(targetChain_f, receiverValue_f, gasLimit_f)] - */ - -function forwardPayloadToEvm( - uint16 targetChain, - address targetAddress, - bytes memory payload, - uint256 receiverValue, - uint256 gasLimit -) external payable; -``` - -> Note: If at least one forward is requested and there doesn’t end up being enough of a refund leftover to complete the forward(s), then the full delivery on chain B will revert, and the status (emitted in an event from the Wormhole Relayer contract) will be ‘FORWARD_REQUEST_FAILURE’. - -If all the forwards requested are able to be executed (i.e. there is enough of a refund leftover such that all of them can be funded), the status will be `FORWARD_REQUEST_SUCCESS` - ## Composing with other Wormhole modules - Requesting Delivery of Existing Wormhole Messages Often times, we wish to deliver a wormhole message that has already been published (by a different contract). diff --git a/src/extensions/HelloWormholeConfirmation.sol b/src/extensions/HelloWormholeConfirmation.sol index b558615..5cd9918 100644 --- a/src/extensions/HelloWormholeConfirmation.sol +++ b/src/extensions/HelloWormholeConfirmation.sol @@ -38,8 +38,6 @@ 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 - // 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 ); @@ -71,8 +69,8 @@ contract HelloWormholeConfirmation is Base, IWormholeReceiver { 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}( + require(msg.value >= confirmationCost, "Didn't receive enough value for the second send!"); + wormholeRelayer.sendPayloadToEvm{value: confirmationCost}( sourceChain, fromWormholeFormat(sourceAddress), abi.encode(MessageType.CONFIRMATION, greeting, sender), diff --git a/src/extensions/HelloWormholeForwarding.sol b/src/extensions/HelloWormholeForwarding.sol deleted file mode 100644 index 25d964e..0000000 --- a/src/extensions/HelloWormholeForwarding.sol +++ /dev/null @@ -1,88 +0,0 @@ -// 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 HelloWormholeForwarding 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; - } - } -} \ No newline at end of file diff --git a/test/extensions/HelloWormholeForwarding.t.sol b/test/extensions/HelloWormholeForwarding.t.sol deleted file mode 100644 index 1731d87..0000000 --- a/test/extensions/HelloWormholeForwarding.t.sol +++ /dev/null @@ -1,58 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import "../../src/extensions/HelloWormholeForwarding.sol"; - -import "wormhole-solidity-sdk/testing/WormholeRelayerTest.sol"; - -contract HelloWormholeForwardingTest is WormholeRelayerBasicTest { - event GreetingReceived(string greeting, uint16 senderChain, address sender); - - HelloWormholeForwarding helloSource; - HelloWormholeForwarding helloTarget; - - function setUpSource() public override { - helloSource = new HelloWormholeForwarding(address(relayerSource), address(wormholeSource)); - } - - function setUpTarget() public override { - helloTarget = new HelloWormholeForwarding(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!"); - } -}