Nice comments in the code (#111)

* resend doesn't take nonce as parameter

* Replace 'delivery failure' with 'receiver failure' in specs

* helper send method

* Fix interface to match not having nonce for resend

* Fix forge tests to not have nonce as a parameter

* fix redeliveryTest

* Split CoreRelayer into CoreRelayer and CoreRelayerDelivery

* fix build error

* forge fmt

* fix interface of getdefaultrelayprovider

* Nice comments for main functions in CoreRelayer.sol

* Nice comments for CoreRelayerMessages

* forge fmt

* merge

* Removes wormholeMessageFee from storage to avoid excessive gas costs.

---------

Co-authored-by: Sebastián Claudio Nale <sebinale@gmail.com>
This commit is contained in:
derpy-duck 2023-02-28 10:09:01 -05:00 committed by GitHub
parent a292dc253d
commit 6fee6ba1e4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 911 additions and 502 deletions

View File

@ -4,28 +4,36 @@
pragma solidity ^0.8.0;
import "../interfaces/IWormholeRelayer.sol";
import "../interfaces/IWormholeReceiver.sol";
import "../interfaces/IDelivery.sol";
import "./CoreRelayerGovernance.sol";
import "./CoreRelayerDelivery.sol";
import "./CoreRelayerStructs.sol";
contract CoreRelayer is CoreRelayerGovernance {
enum DeliveryStatus {
SUCCESS,
RECEIVER_FAILURE,
FORWARD_REQUEST_FAILURE,
FORWARD_REQUEST_SUCCESS,
INVALID_REDELIVERY
}
event Delivery(
address indexed recipientContract,
uint16 indexed sourceChain,
uint64 indexed sequence,
bytes32 deliveryVaaHash,
DeliveryStatus status
);
contract CoreRelayer is CoreRelayerDelivery {
/**
* @notice This 'send' function emits a wormhole message that alerts the default wormhole relay provider to
* call the receiveWormholeMessage(bytes[] memory vaas, bytes[] memory additionalData) endpoint of the contract on chain 'targetChain' and address 'targetAddress'
* with the first argument being all of the wormhole message in the current transaction that have nonce 'nonce' (which have additionally been encoded and signed by the Guardian set to form 'VAAs'),
* (including the one emitted from this function, which can be ignored)
* (these messages will be ordered in the 'vaas' array in the order they were emitted in the source transaction)
* and with the second argument empty
*
*
* @param targetChain The chain that the vaas are delivered to, in Wormhole Chain ID format
* @param targetAddress The address (in Wormhole 32-byte format) on chain 'targetChain' of the contract to which the vaas are delivered.
* This contract must implement the IWormholeReceiver interface, which simply requires a 'receiveWormholeMessage(bytes[] memory vaas, bytes[] memory additionalData)' endpoint
* @param refundAddress The address (in Wormhole 32-byte format) on chain 'targetChain' to which any leftover funds (that weren't used for target chain gas or passed into targetAddress as value) should be sent
* @param maxTransactionFee The maximum amount (denominated in source chain (this chain) currency) that you wish to spend on funding gas for the target chain.
* If more gas is needed on the target chain than is paid for, there will be a Receiver Failure.
* Any unused value out of this fee will be refunded to 'refundAddress'
* If maxTransactionFee >= quoteGas(targetChain, gasLimit, getDefaultRelayProvider()), then as long as 'targetAddress''s receiveWormholeMessage function uses at most 'gasLimit' units of gas (and doesn't revert), the delivery will succeed
* @param receiverValue The amount (denominated in source chain currency) that will be converted to target chain currency and passed into the receiveWormholeMessage endpoint as value.
* If receiverValue >= quoteReceiverValue(targetChain, targetAmount, getDefaultRelayProvider()), then at least 'targetAmount' of targetChain currency will be passed into the 'receiveWormholeFunction' as value.
* @param nonce The messages to be relayed are all of the emitted wormhole messages in the current transaction that have nonce 'nonce'.
*
* This function must be called with a payment of at least maxTransactionFee + receiverValue + one wormhole message fee.
*
* @return sequence The sequence number for the emitted wormhole message, which contains encoded delivery instructions meant for the default wormhole relay provider.
* The relay provider will listen for these messages, and then execute the delivery as described.
*/
function send(
uint16 targetChain,
bytes32 targetAddress,
@ -43,58 +51,145 @@ contract CoreRelayer is CoreRelayerGovernance {
);
}
/**
* @notice This 'send' function emits a wormhole message that alerts a relay provider to
* call the receiveWormholeMessage(bytes[] memory vaas, bytes[] memory additionalData) endpoint of the contract on chain 'targetChain' and address 'targetAddress'
* with the first argument being all of the wormhole message in the current transaction that have nonce 'nonce' (which have additionally been encoded and signed by the Guardian set to form 'VAAs'),
* (including the one emitted from this function, which can be ignored)
* (these messages will be ordered in the 'vaas' array in the order they were emitted in the source transaction)
* and with the second argument empty
*
*
* @param request The Send request containing info about the targetChain, targetAddress, refundAddress, maxTransactionFee, receiverValue, and relayParameters
* @param nonce The messages to be relayed are all of the emitted wormhole messages in the current transaction that have nonce 'nonce'.
* @param relayProvider The address of (the relay provider you wish to deliver the messages)'s contract on this source chain. This must be a contract that implements IRelayProvider.
* If request.maxTransactionFee >= quoteGas(request.targetChain, gasLimit, relayProvider),
* then as long as 'request.targetAddress''s receiveWormholeMessage function uses at most 'gasLimit' units of gas (and doesn't revert), the delivery will succeed
* If request.receiverValue >= quoteReceiverValue(request.targetChain, targetAmount, relayProvider), then at least 'targetAmount' of targetChain currency will be passed into the 'receiveWormholeFunction' as value.
* To use the default relay provider, set this field to be getDefaultRelayProvider()
*
* This function must be called with a payment of at least request.maxTransactionFee + request.receiverValue + one wormhole message fee.
*
* @return sequence The sequence number for the emitted wormhole message, which contains encoded delivery instructions meant for your specified relay provider.
* The relay provider will listen for these messages, and then execute the delivery as described.
*/
function send(IWormholeRelayer.Send memory request, uint32 nonce, address relayProvider)
public
payable
returns (uint64 sequence)
{
// call multichainSend with one 'Send' in the requests array
sequence = multichainSend(multichainSendContainer(request, relayProvider), nonce);
}
function forward(IWormholeRelayer.Send memory request, uint32 nonce, address relayProvider) public payable {
multichainForward(multichainSendContainer(request, relayProvider), nonce);
}
function resend(IWormholeRelayer.ResendByTx memory request, address relayProvider)
public
payable
returns (uint64 sequence)
{
updateWormholeMessageFee();
bool isSufficient = request.newMaxTransactionFee + request.newReceiverValue + wormholeMessageFee() <= msg.value;
if (!isSufficient) {
revert IWormholeRelayer.MsgValueTooLow();
}
IRelayProvider provider = IRelayProvider(relayProvider);
RedeliveryByTxHashInstruction memory instruction = convertResendToRedeliveryInstruction(request, provider);
checkRedeliveryInstruction(instruction, provider);
sequence = wormhole().publishMessage{value: wormholeMessageFee()}(
0, encodeRedeliveryInstruction(instruction), provider.getConsistencyLevel()
/**
* @notice This 'forward' function can only be called in a IWormholeReceiver within the 'receiveWormholeMessages' function
* It's purpose is to use any leftover fee from the 'maxTransactionFee' of the current delivery to fund another delivery
*
* Specifically, suppose an integrator requested a Send (with parameters oldTargetChain, oldTargetAddress, etc)
* and sets quoteGas(oldTargetChain, gasLimit, oldRelayProvider) as 'maxTransactionFee' in a Send,
* but during the delivery on oldTargetChain, the call to oldTargetAddress's receiveWormholeMessages endpoint uses only x units of gas (where x < gasLimit).
*
* Normally, (gasLimit - x)/gasLimit * oldMaxTransactionFee, converted to target chain currency, would be refunded to 'oldRefundAddress'.
* However, if during execution of receiveWormholeMessage the integrator made a call to forward,
*
* We instead would use [(gasLimit - x)/gasLimit * oldMaxTransactionFee, converted to target chain currency] + (any additional funds passed into forward)
* to fund a new delivery (of wormhole messages emitted during execution of oldTargetAddress's receiveWormholeMessages) that is requested in the call to 'forward'.
*
* Specifically, this 'forward' function is only callable within a delivery (during receiveWormholeMessages) and indicates the in-progress delivery to use any leftover funds from the current delivery to fund a new delivery
* or equivalently, indicates the in-progress delivery to call the receiveWormholeMessage(bytes[] memory vaas, bytes[] memory additionalData) endpoint of the contract on chain 'targetChain' and address 'targetAddress'
* with the first argument being all of the wormhole message in the current transaction that have nonce 'nonce' (which have additionally been encoded and signed by the Guardian set to form 'VAAs'),
* (which will be all of the wormhole messages emitted during the execution of oldTargetAddress's receiveWormholeMessages in the order that they were emitted, as well as one wormhole message *that is always at the end* that can be ignored)
* and with the second argument empty
*
* @param targetChain The chain that the vaas are delivered to, in Wormhole Chain ID format
* @param targetAddress The address (in Wormhole 32-byte format) on chain 'targetChain' of the contract to which the vaas are delivered.
* This contract must implement the IWormholeReceiver interface, which simply requires a 'receiveWormholeMessage(bytes[] memory vaas, bytes[] memory additionalData)' endpoint
* @param refundAddress The address (in Wormhole 32-byte format) on chain 'targetChain' to which any leftover funds (that weren't used for target chain gas or passed into targetAddress as value) should be sent
* @param maxTransactionFee The maximum amount (denominated in source chain (this chain) currency) that you wish to spend on funding gas for the target chain.
* If more gas is needed on the target chain than is paid for, there will be a Receiver Failure.
* Any unused value out of this fee will be refunded to 'refundAddress'
* If maxTransactionFee >= quoteGas(targetChain, gasLimit, getDefaultRelayProvider()), then as long as 'targetAddress''s receiveWormholeMessage function uses at most 'gasLimit' units of gas (and doesn't revert), the delivery will succeed
* @param receiverValue The amount (denominated in source chain currency) that will be converted to target chain currency and passed into the receiveWormholeMessage endpoint as value.
* If receiverValue >= quoteReceiverValue(targetChain, targetAmount, getDefaultRelayProvider()), then at least 'targetAmount' of targetChain currency will be passed into the 'receiveWormholeFunction' as value.
* @param nonce The messages to be relayed are all of the emitted wormhole messages in the current transaction that have nonce 'nonce'.
*
* This forward will succeed if (leftover funds from the current delivery that would have been refunded) + (any extra msg.value passed into forward) is at least maxTransactionFee + receiverValue + one wormhole message fee.
*/
function forward(
uint16 targetChain,
bytes32 targetAddress,
bytes32 refundAddress,
uint256 maxTransactionFee,
uint256 receiverValue,
uint32 nonce
) external payable {
forward(
IWormholeRelayer.Send(
targetChain, targetAddress, refundAddress, maxTransactionFee, receiverValue, getDefaultRelayParams()
),
nonce,
getDefaultRelayProvider()
);
//Send the delivery fees to the specified address of the provider.
pay(provider.getRewardAddress(), msg.value - wormholeMessageFee());
}
/**
* TODO: Correct this comment
* @dev `multisend` generates a VAA with DeliveryInstructions to be delivered to the specified target
* contract based on user parameters.
* it parses the RelayParameters to determine the target chain ID
* it estimates the cost of relaying the batch
* it confirms that the user has passed enough value to pay the relayer
* it checks that the passed nonce is not zero (VAAs with a nonce of zero will not be batched)
* it generates a VAA with the encoded DeliveryInstructions
* @notice This 'forward' function can only be called in a IWormholeReceiver within the 'receiveWormholeMessages' function
* It's purpose is to use any leftover fee from the 'maxTransactionFee' of the current delivery to fund another delivery
*
* Specifically, suppose an integrator requested a Send (with parameters oldTargetChain, oldTargetAddress, etc)
* and sets quoteGas(oldTargetChain, gasLimit, oldRelayProvider) as 'maxTransactionFee' in a Send,
* but during the delivery on oldTargetChain, the call to oldTargetAddress's receiveWormholeMessages endpoint uses only x units of gas (where x < gasLimit).
*
* Normally, (gasLimit - x)/gasLimit * oldMaxTransactionFee, converted to target chain currency, would be refunded to 'oldRefundAddress'.
* However, if during execution of receiveWormholeMessage the integrator made a call to forward,
*
* We instead would use [(gasLimit - x)/gasLimit * oldMaxTransactionFee, converted to target chain currency] + (any additional funds passed into forward)
* to fund a new delivery (of wormhole messages emitted during execution of oldTargetAddress's receiveWormholeMessages) that is requested in the call to 'forward'.
*
* Specifically, this 'forward' function is only callable within a delivery (during receiveWormholeMessages) and indicates the in-progress delivery to use any leftover funds from the current delivery to fund a new delivery
* or equivalently, indicates the in-progress delivery to call the receiveWormholeMessage(bytes[] memory vaas, bytes[] memory additionalData) endpoint of the contract on chain 'targetChain' and address 'targetAddress'
* with the first argument being all of the wormhole message in the current transaction that have nonce 'nonce' (which have additionally been encoded and signed by the Guardian set to form 'VAAs'),
* (which will be all of the wormhole messages emitted during the execution of oldTargetAddress's receiveWormholeMessages in the order that they were emitted, as well as one wormhole message *that is always at the end* that can be ignored)
* and with the second argument empty
*
* @param request The Send request containing info about the targetChain, targetAddress, refundAddress, maxTransactionFee, receiverValue, and relayParameters
* (specifically, the send info that will be used to deliver all of the wormhole messages emitted during the execution of oldTargetAddress's receiveWormholeMessages)
* This forward will succeed if (leftover funds from the current delivery that would have been refunded) + (any extra msg.value passed into forward) is at least maxTransactionFee + receiverValue + one wormhole message fee.
* @param nonce The messages to be relayed are all of the emitted wormhole messages in the current transaction (during execution of oldTargetAddress's receiveWormholeMessages) that have nonce 'nonce'.
* @param relayProvider The address of (the relay provider you wish to deliver the messages)'s contract on this source chain. This must be a contract that implements IRelayProvider.
* If request.maxTransactionFee >= quoteGas(request.targetChain, gasLimit, relayProvider),
* then as long as 'request.targetAddress''s receiveWormholeMessage function uses at most 'gasLimit' units of gas (and doesn't revert), the delivery will succeed
* If request.receiverValue >= quoteReceiverValue(request.targetChain, targetAmount, relayProvider), then at least 'targetAmount' of targetChain currency will be passed into the 'receiveWormholeFunction' as value.
* To use the default relay provider, set this field to be getDefaultRelayProvider()
*
* This function must be called with a payment of at least request.maxTransactionFee + request.receiverValue + one wormhole message fee.
*/
function multichainSend(IWormholeRelayer.MultichainSend memory deliveryRequests, uint32 nonce)
function forward(IWormholeRelayer.Send memory request, uint32 nonce, address relayProvider) public payable {
// call multichainForward with one 'Send' in the requests array
multichainForward(multichainSendContainer(request, relayProvider), nonce);
}
/**
* @notice The multichainSend function delivers all wormhole messages in the current transaction of nonce 'nonce' to many destinations,
* with each destination specified in a Send struct, describing the desired targetAddress, targetChain, maxTransactionFee, receiverValue, refundAddress, and relayParameters
*
* @param sendContainer The MultichainSend struct, containing the array of Send requests, as well as the desired relayProviderAddress
* @param nonce The messages to be relayed are all of the emitted wormhole messages in the current transaction that have nonce 'nonce'
*
* This function must be called with a payment of at least (one wormhole message fee) + Sum_(i=0 -> sendContainer.requests.length - 1) [sendContainer.requests[i].maxTransactionFee + sendContainer.requests[i].receiverValue].
*
* @return sequence The sequence number for the emitted wormhole message, which contains encoded delivery instructions meant for the default wormhole relay provider.
* The relay provider will listen for these messages, and then execute the delivery as described
*/
function multichainSend(IWormholeRelayer.MultichainSend memory sendContainer, uint32 nonce)
public
payable
returns (uint64 sequence)
{
updateWormholeMessageFee();
uint256 totalFee = getTotalFeeMultichainSend(deliveryRequests);
IWormhole wormhole = wormhole();
uint256 wormholeMessageFee = wormhole.messageFee();
uint256 totalFee = getTotalFeeMultichainSend(sendContainer, wormholeMessageFee);
if (totalFee > msg.value) {
revert IWormholeRelayer.MsgValueTooLow();
}
@ -102,33 +197,49 @@ contract CoreRelayer is CoreRelayerGovernance {
revert IWormholeRelayer.NonceIsZero();
}
IRelayProvider relayProvider = IRelayProvider(deliveryRequests.relayProviderAddress);
DeliveryInstructionsContainer memory container =
convertMultichainSendToDeliveryInstructionsContainer(deliveryRequests);
checkInstructions(container, IRelayProvider(deliveryRequests.relayProviderAddress));
container.sufficientlyFunded = true;
IRelayProvider relayProvider = IRelayProvider(sendContainer.relayProviderAddress);
// emit delivery message
sequence = wormhole().publishMessage{value: wormholeMessageFee()}(
nonce, encodeDeliveryInstructionsContainer(container), relayProvider.getConsistencyLevel()
// For each 'Send' request,
// calculate how much gas the relay provider can pay for on 'request.targetChain' using 'request.newTransactionFee',
// and calculate how much value the relay provider will pass into 'request.targetAddress'
DeliveryInstructionsContainer memory instructionsContainer =
convertMultichainSendToDeliveryInstructionsContainer(sendContainer);
// For each 'Send' request,
// Check that the total amount of value the relay provider needs to use for this send is <= the relayProvider's maximum budget for 'targetChain'
// and check that the calculated gas is greater than 0
checkInstructions(instructionsContainer, IRelayProvider(sendContainer.relayProviderAddress));
// Mark the container as 'sufficientlyFunded', since the user passed in msg.value to fund all of the requests
instructionsContainer.sufficientlyFunded = true;
// Publish a wormhole message indicating to the relay provider (who is watching wormhole messages from this contract)
// to relay the messages from this transaction (of nonce 'nonce') to the specified chains, each with the calculated amount of gas and receiverValue
sequence = wormhole.publishMessage{value: wormholeMessageFee}(
nonce, encodeDeliveryInstructionsContainer(instructionsContainer), relayProvider.getConsistencyLevel()
);
//pay fee to provider
pay(relayProvider.getRewardAddress(), totalFee - wormholeMessageFee());
// Pay the relay provider
pay(relayProvider.getRewardAddress(), totalFee - wormholeMessageFee);
}
/**
* TODO correct this comment
* @dev `forward` queues up a 'send' which will be executed after the present delivery is complete
* & uses the gas refund to cover the costs.
* contract based on user parameters.
* it parses the RelayParameters to determine the target chain ID
* it estimates the cost of relaying the batch
* it confirms that the user has passed enough value to pay the relayer
* it checks that the passed nonce is not zero (VAAs with a nonce of zero will not be batched)
* it generates a VAA with the encoded DeliveryInstructions
* @notice The multichainForward function can only be called in a IWormholeReceiver within the 'receiveWormholeMessages' function
* It's purpose is to use any leftover fee from the 'maxTransactionFee' of the current delivery to fund another delivery, specifically a multichain delivery to many destinations
* See the description of 'forward' for further explanation of what a forward is.
* multichainForward provides the same functionality of forward, while additionally allowing the same array of wormhole messages to be sent to many destinations
*
* Let LEFTOVER_VALUE = (leftover funds from the current delivery that would have been refunded) + (any extra msg.value passed into forward)
* and let NEEDED_VALUE = (one wormhole message fee) + Sum_(i=0 -> sendContainer.requests.length - 1) [sendContainer.requests[i].maxTransactionFee + sendContainer.requests[i].receiverValue].
* The multichainForward will succeed if LEFTOVER_VALUE >= NEEDED_VALUE
*
* note: If LEFTOVER_VALUE > NEEDED_VALUE, then the maxTransactionFee of the first request in the array of sends will be incremented by 'LEFTOVER_VALUE - NEEDED_VALUE'
*
* @param sendContainer The MultichainSend struct, containing the array of Send requests, as well as the desired relayProviderAddress
* @param nonce The messages to be relayed are all of the emitted wormhole messages in the current transaction that have nonce 'nonce'
*
*/
function multichainForward(IWormholeRelayer.MultichainSend memory deliveryRequests, uint32 nonce) public payable {
function multichainForward(IWormholeRelayer.MultichainSend memory sendContainer, uint32 nonce) public payable {
if (!isContractLocked()) {
revert IWormholeRelayer.NoDeliveryInProgress();
}
@ -142,11 +253,22 @@ contract CoreRelayer is CoreRelayerGovernance {
revert IWormholeRelayer.ForwardRequestFromWrongAddress();
}
uint256 totalFee = getTotalFeeMultichainSend(deliveryRequests);
DeliveryInstructionsContainer memory container =
convertMultichainSendToDeliveryInstructionsContainer(deliveryRequests);
checkInstructions(container, IRelayProvider(deliveryRequests.relayProviderAddress));
uint256 wormholeMessageFee = wormhole().messageFee();
uint256 totalFee = getTotalFeeMultichainSend(sendContainer, wormholeMessageFee);
// For each 'Send' request,
// calculate how much gas the relay provider can pay for on 'request.targetChain' using 'request.newTransactionFee',
// and calculate how much value the relay provider will pass into 'request.targetAddress'
DeliveryInstructionsContainer memory container =
convertMultichainSendToDeliveryInstructionsContainer(sendContainer);
// For each 'Send' request,
// Check that the total amount of value the relay provider needs to use for this send is <= the relayProvider's maximum budget for 'targetChain'
// and check that the calculated gas is greater than 0
checkInstructions(container, IRelayProvider(sendContainer.relayProviderAddress));
// Save information about the forward in state, so it can be processed after the execution of 'receiveWormholeMessages',
// because we will then know how much of the 'maxTransactionFee' of the current delivery is still available for use in this forward
setForwardInstruction(
ForwardInstruction({
container: container,
@ -154,59 +276,154 @@ contract CoreRelayer is CoreRelayerGovernance {
msgValue: msg.value,
totalFee: totalFee,
sender: msg.sender,
relayProvider: deliveryRequests.relayProviderAddress,
relayProvider: sendContainer.relayProviderAddress,
isValid: true
})
);
}
function emitForward(uint256 transactionFeeRefundAmount, ForwardInstruction memory forwardInstruction)
internal
returns (bool forwardIsFunded)
/**
* @notice This 'resend' function emits a wormhole message requesting to resend an array of messages that have been previously requested to be sent
* Specifically, if a user in transaction 'txHash' on chain 'sourceChain' emits many wormhole messages of nonce 'sourceNonce' and then
* makes a call to 'send' requesting these messages to be sent to 'targetAddress' on 'targetChain',
* then the user can request a redelivery of these wormhole messages any time in the future through a call to 'resend' using this function
*
* @param request Information about the resend request, including the source chain and source transaction hash,
* @param relayProvider The address of (the relay provider you wish to deliver the messages)'s contract on this source chain. This must be a contract that implements IRelayProvider.
* If the targetAddress's receiveWormholeMessage function uses 'gasLimit' units of gas, then we must have newMaxTransactionFee >= quoteGasResend(targetChain, gasLimit, relayProvider)
*
* @return sequence The sequence number for the emitted wormhole message, which contains encoded delivery instructions meant for your specified relay provider.
* The relay provider will listen for these messages, and then execute the redelivery as described
*/
function resend(IWormholeRelayer.ResendByTx memory request, address relayProvider)
public
payable
returns (uint64 sequence)
{
DeliveryInstructionsContainer memory container = forwardInstruction.container;
//Add any additional funds which were passed in to the refund amount
transactionFeeRefundAmount = transactionFeeRefundAmount + forwardInstruction.msgValue;
//make sure the refund amount covers the native gas amounts
forwardIsFunded = (transactionFeeRefundAmount >= forwardInstruction.totalFee);
container.sufficientlyFunded = forwardIsFunded;
IRelayProvider relayProvider = IRelayProvider(forwardInstruction.relayProvider);
if (forwardIsFunded) {
// the rollover chain is the chain in the first request
uint256 amountUnderMaximum = relayProvider.quoteMaximumBudget(container.instructions[0].targetChain)
- (
wormholeMessageFee() + container.instructions[0].maximumRefundTarget
+ container.instructions[0].receiverValueTarget
);
uint256 convertedExtraAmount = calculateTargetDeliveryMaximumRefund(
container.instructions[0].targetChain,
transactionFeeRefundAmount - forwardInstruction.totalFee,
relayProvider
);
container.instructions[0].maximumRefundTarget +=
(amountUnderMaximum > convertedExtraAmount) ? convertedExtraAmount : amountUnderMaximum;
IWormhole wormhole = wormhole();
uint256 wormholeMessageFee = wormhole.messageFee();
if (request.newMaxTransactionFee + request.newReceiverValue + wormholeMessageFee > msg.value) {
revert IWormholeRelayer.MsgValueTooLow();
}
//emit forwarding instruction
wormhole().publishMessage{value: wormholeMessageFee()}(
forwardInstruction.nonce,
encodeDeliveryInstructionsContainer(container),
relayProvider.getConsistencyLevel()
IRelayProvider provider = IRelayProvider(relayProvider);
// Calculate how much gas the relay provider can pay for on 'request.targetChain' using 'request.newTransactionFee',
// and calculate how much value the relay provider will pass into 'request.targetAddress'
RedeliveryByTxHashInstruction memory instruction = convertResendToRedeliveryInstruction(request, provider);
// Check that the total amount of value the relay provider needs to use for this redelivery is <= the relayProvider's maximum budget for 'targetChain'
// and check that the calculated gas is greater than 0
checkRedeliveryInstruction(instruction, provider, wormholeMessageFee);
// Publish a wormhole message indicating to the relay provider (who is watching wormhole messages from this contract)
// to re-relay the messages from transaction 'request.txHash' with the calculated amount of gas and receiverValue
sequence = wormhole.publishMessage{value: wormholeMessageFee}(
0, encodeRedeliveryInstruction(instruction), provider.getConsistencyLevel()
);
// if funded, pay out reward to provider. Otherwise, the delivery code will handle sending a refund.
if (forwardIsFunded) {
pay(relayProvider.getRewardAddress(), transactionFeeRefundAmount);
}
//clear forwarding request from cache
clearForwardInstruction();
// Pay the relay provider
pay(provider.getRewardAddress(), msg.value - wormholeMessageFee);
}
/**
* @notice quoteGas tells you how much maxTransactionFee (denominated in current (source) chain currency) must be in order to fund a call to
* receiveWormholeMessages on a contract on chain 'targetChain' that uses 'gasLimit' units of gas
*
* Specifically, for a Send 'request',
* If 'request.targetAddress''s receiveWormholeMessage function uses 'gasLimit' units of gas,
* then we must have request.maxTransactionFee >= quoteGas(request.targetChain, gasLimit, relayProvider)
*
* @param targetChain the target chain that you wish to use gas on
* @param gasLimit the amount of gas you wish to use
* @param relayProvider The address of (the relay provider you wish to deliver the messages)'s contract on this source chain. This must be a contract that implements IRelayProvider.
*
* @return maxTransactionFee The 'maxTransactionFee' you pass into your request (to relay messages to 'targetChain' and use 'gasLimit' units of gas) must be at least this amount
*/
function quoteGas(uint16 targetChain, uint32 gasLimit, address relayProvider)
public
view
returns (uint256 maxTransactionFee)
{
IRelayProvider provider = IRelayProvider(relayProvider);
// maxTransactionFee is a linear function of the amount of gas desired
maxTransactionFee =
provider.quoteDeliveryOverhead(targetChain) + (gasLimit * provider.quoteGasPrice(targetChain));
}
/**
* @notice quoteGasResend tells you how much maxTransactionFee (denominated in current (source) chain currency) must be in order to fund a *resend* call to
* receiveWormholeMessages on a contract on chain 'targetChain' that uses 'gasLimit' units of gas
*
* Specifically, for a ResendByTx 'request',
* If 'request.targetAddress''s receiveWormholeMessage function uses 'gasLimit' units of gas,
* then we must have request.maxTransactionFee >= quoteGasResend(request.targetChain, gasLimit, relayProvider)
*
* @param targetChain the target chain that you wish to use gas on
* @param gasLimit the amount of gas you wish to use
* @param relayProvider The address of (the relay provider you wish to deliver the messages)'s contract on this source chain. This must be a contract that implements IRelayProvider.
*
* @return maxTransactionFee The 'maxTransactionFee' you pass into your resend request (to relay messages to 'targetChain' and use 'gasLimit' units of gas) must be at least this amount
*/
function quoteGasResend(uint16 targetChain, uint32 gasLimit, address relayProvider)
public
view
returns (uint256 maxTransactionFee)
{
IRelayProvider provider = IRelayProvider(relayProvider);
// maxTransactionFee is a linear function of the amount of gas desired
maxTransactionFee =
provider.quoteRedeliveryOverhead(targetChain) + (gasLimit * provider.quoteGasPrice(targetChain));
}
/**
* @notice quoteReceiverValue tells you how much receiverValue (denominated in current (source) chain currency) must be
* in order for the relay provider to pass in 'targetAmount' as msg value when calling receiveWormholeMessages.
*
* Specifically, for a send 'request',
* In order for 'request.targetAddress''s receiveWormholeMessage function to be called with 'targetAmount' of value,
* then we must have request.receiverValue >= quoteReceiverValue(request.targetChain, targetAmount, relayProvider)
*
* @param targetChain the target chain that you wish to receive value on
* @param targetAmount the amount of value you wish to be passed into receiveWormholeMessages
* @param relayProvider The address of (the relay provider you wish to deliver the messages)'s contract on this source chain. This must be a contract that implements IRelayProvider.
*
* @return receiverValue The 'receiverValue' you pass into your send request (to relay messages to 'targetChain' with 'targetAmount' of value) must be at least this amount
*/
function quoteReceiverValue(uint16 targetChain, uint256 targetAmount, address relayProvider)
public
view
returns (uint256 receiverValue)
{
IRelayProvider provider = IRelayProvider(relayProvider);
// Converts 'targetAmount' from target chain currency to source chain currency (using relayProvider's prices)
// and applies a multiplier of '1 + (buffer / denominator)'
(uint16 buffer, uint16 denominator) = provider.getAssetConversionBuffer(targetChain);
receiverValue = assetConversionHelper(
targetChain, targetAmount, chainId(), uint256(0) + denominator + buffer, denominator, true, provider
);
}
/**
* @notice Returns the address of the current default relay provider
* @return relayProvider The address of (the default relay provider)'s contract on this source chain. This must be a contract that implements IRelayProvider.
*/
function getDefaultRelayProvider() public view returns (address relayProvider) {
relayProvider = defaultRelayProvider();
}
/**
* @notice Returns default relay parameters
* @return relayParams default relay parameters
*/
function getDefaultRelayParams() public pure returns (bytes memory relayParams) {
return new bytes(0);
}
// Helper to put one Send struct into a MultichainSend struct
function multichainSendContainer(IWormholeRelayer.Send memory request, address relayProvider)
internal
pure
@ -216,295 +433,4 @@ contract CoreRelayer is CoreRelayerGovernance {
requests[0] = request;
container = IWormholeRelayer.MultichainSend({relayProviderAddress: relayProvider, requests: requests});
}
function _executeDelivery(
DeliveryInstruction memory internalInstruction,
bytes[] memory encodedVMs,
bytes32 deliveryVaaHash,
address payable relayerRefund,
uint16 sourceChain,
uint64 sourceSequence
) internal {
//REVISE Decide whether we want to remove the DeliveryInstructionsContainer from encodedVMs.
// lock the contract to prevent reentrancy
if (isContractLocked()) {
revert IDelivery.ReentrantCall();
}
setContractLock(true);
setLockedTargetAddress(fromWormholeFormat(internalInstruction.targetAddress));
// store gas budget pre target invocation to calculate unused gas budget
uint256 preGas = gasleft();
// call the receiveWormholeMessages endpoint on the target contract
(bool callToTargetContractSucceeded,) = fromWormholeFormat(internalInstruction.targetAddress).call{
gas: internalInstruction.executionParameters.gasLimit,
value: internalInstruction.receiverValueTarget
}(abi.encodeCall(IWormholeReceiver.receiveWormholeMessages, (encodedVMs, new bytes[](0))));
uint256 postGas = gasleft();
// There's no easy way to measure the exact cost of the CALL instruction.
// This is due to the fact that the compiler probably emits DUPN or MSTORE instructions
// to setup the arguments for the call just after our measurement.
// This means the refund could be off by a few units of gas.
// Thus, we ensure the overhead doesn't cause an overflow in our refund formula here.
uint256 gasUsed = (preGas - postGas) > internalInstruction.executionParameters.gasLimit
? internalInstruction.executionParameters.gasLimit
: (preGas - postGas);
// refund unused gas budget
uint256 transactionFeeRefundAmount = (internalInstruction.executionParameters.gasLimit - gasUsed)
* internalInstruction.maximumRefundTarget / internalInstruction.executionParameters.gasLimit;
// unlock the contract
setContractLock(false);
ForwardInstruction memory forwardingRequest = getForwardInstruction();
DeliveryStatus status;
bool forwardIsFunded = false;
if (forwardingRequest.isValid) {
forwardIsFunded = emitForward(transactionFeeRefundAmount, forwardingRequest);
status = forwardIsFunded ? DeliveryStatus.FORWARD_REQUEST_SUCCESS : DeliveryStatus.FORWARD_REQUEST_FAILURE;
} else {
status = callToTargetContractSucceeded ? DeliveryStatus.SUCCESS : DeliveryStatus.RECEIVER_FAILURE;
}
uint256 receiverValueRefundAmount =
(callToTargetContractSucceeded ? 0 : internalInstruction.receiverValueTarget);
uint256 refundToRefundAddress = receiverValueRefundAmount + (forwardIsFunded ? 0 : transactionFeeRefundAmount);
bool refundPaidToRefundAddress =
pay(payable(fromWormholeFormat(internalInstruction.refundAddress)), refundToRefundAddress);
emit Delivery({
recipientContract: fromWormholeFormat(internalInstruction.targetAddress),
sourceChain: sourceChain,
sequence: sourceSequence,
deliveryVaaHash: deliveryVaaHash,
status: status
});
uint256 extraRelayerFunds = (
msg.value - internalInstruction.receiverValueTarget - internalInstruction.maximumRefundTarget
- wormholeMessageFee()
);
uint256 relayerRefundAmount = extraRelayerFunds
+ (internalInstruction.maximumRefundTarget - transactionFeeRefundAmount)
+ (forwardingRequest.isValid ? 0 : wormholeMessageFee())
+ (refundPaidToRefundAddress ? 0 : refundToRefundAddress);
// refund the rest to relayer
pay(relayerRefund, relayerRefundAmount);
}
function verifyRelayerVM(IWormhole.VM memory vm) internal view returns (bool) {
return registeredCoreRelayerContract(vm.emitterChainId) == vm.emitterAddress;
}
function getDefaultRelayProvider() public view returns (address) {
return defaultRelayProvider();
}
function redeliverSingle(IDelivery.TargetRedeliveryByTxHashParamsSingle memory targetParams) public payable {
//cache wormhole
IWormhole wormhole = wormhole();
updateWormholeMessageFee();
//validate the redelivery VM
(IWormhole.VM memory redeliveryVM, bool valid, string memory reason) =
wormhole.parseAndVerifyVM(targetParams.redeliveryVM);
if (!valid) {
revert IDelivery.InvalidRedeliveryVM(reason);
}
if (!verifyRelayerVM(redeliveryVM)) {
// Redelivery VM has an invalid emitter
revert IDelivery.InvalidEmitterInRedeliveryVM();
}
RedeliveryByTxHashInstruction memory redeliveryInstruction = decodeRedeliveryInstruction(redeliveryVM.payload);
//validate the original delivery VM
IWormhole.VM memory originalDeliveryVM;
(originalDeliveryVM, valid, reason) =
wormhole.parseAndVerifyVM(targetParams.sourceEncodedVMs[redeliveryInstruction.deliveryIndex]);
if (!valid) {
revert IDelivery.InvalidVaa(redeliveryInstruction.deliveryIndex, reason);
}
if (!verifyRelayerVM(originalDeliveryVM)) {
// Original Delivery VM has a invalid emitter
revert IDelivery.InvalidEmitterInOriginalDeliveryVM(redeliveryInstruction.deliveryIndex);
}
DeliveryInstruction memory instruction;
(instruction, valid) = validateRedeliverySingle(
redeliveryInstruction,
decodeDeliveryInstructionsContainer(originalDeliveryVM.payload).instructions[redeliveryInstruction
.multisendIndex]
);
if (!valid) {
emit Delivery({
recipientContract: fromWormholeFormat(instruction.targetAddress),
sourceChain: redeliveryVM.emitterChainId,
sequence: redeliveryVM.sequence,
deliveryVaaHash: redeliveryVM.hash,
status: DeliveryStatus.INVALID_REDELIVERY
});
pay(targetParams.relayerRefundAddress, msg.value);
return;
}
_executeDelivery(
instruction,
targetParams.sourceEncodedVMs,
originalDeliveryVM.hash,
targetParams.relayerRefundAddress,
originalDeliveryVM.emitterChainId,
originalDeliveryVM.sequence
);
}
function validateRedeliverySingle(
RedeliveryByTxHashInstruction memory redeliveryInstruction,
DeliveryInstruction memory originalInstruction
) internal view returns (DeliveryInstruction memory deliveryInstruction, bool isValid) {
// All the same checks as delivery single, with a couple additional
// The same relay provider must be specified when doing a single VAA redeliver.
address providerAddress = fromWormholeFormat(redeliveryInstruction.executionParameters.providerDeliveryAddress);
if (providerAddress != fromWormholeFormat(originalInstruction.executionParameters.providerDeliveryAddress)) {
revert IDelivery.MismatchingRelayProvidersInRedelivery();
}
// relayer must have covered the necessary funds
if (
msg.value
< redeliveryInstruction.newMaximumRefundTarget + redeliveryInstruction.newReceiverValueTarget
+ wormholeMessageFee()
) {
revert IDelivery.InsufficientRelayerFunds();
}
uint16 whChainId = chainId();
// msg.sender must be the provider
// "Relay provider differed from the specified address");
isValid = msg.sender == providerAddress
// redelivery must target this chain
// "Redelivery request does not target this chain.");
&& whChainId == redeliveryInstruction.targetChain
// original delivery must target this chain
// "Original delivery request did not target this chain.");
&& whChainId == originalInstruction.targetChain
// gasLimit & receiverValue must be at least as large as the initial delivery
// "New receiver value is smaller than the original"
&& originalInstruction.receiverValueTarget <= redeliveryInstruction.newReceiverValueTarget
// "New gasLimit is smaller than the original"
&& originalInstruction.executionParameters.gasLimit <= redeliveryInstruction.executionParameters.gasLimit;
// Overwrite compute budget and application budget on the original request and proceed.
deliveryInstruction = originalInstruction;
deliveryInstruction.maximumRefundTarget = redeliveryInstruction.newMaximumRefundTarget;
deliveryInstruction.receiverValueTarget = redeliveryInstruction.newReceiverValueTarget;
deliveryInstruction.executionParameters = redeliveryInstruction.executionParameters;
}
function deliverSingle(IDelivery.TargetDeliveryParametersSingle memory targetParams) public payable {
// cache wormhole instance
IWormhole wormhole = wormhole();
updateWormholeMessageFee();
// validate the deliveryIndex
(IWormhole.VM memory deliveryVM, bool valid, string memory reason) =
wormhole.parseAndVerifyVM(targetParams.encodedVMs[targetParams.deliveryIndex]);
if (!valid) {
revert IDelivery.InvalidVaa(targetParams.deliveryIndex, reason);
}
if (!verifyRelayerVM(deliveryVM)) {
revert IDelivery.InvalidEmitter();
}
DeliveryInstructionsContainer memory container = decodeDeliveryInstructionsContainer(deliveryVM.payload);
//ensure this is a funded delivery, not a failed forward.
if (!container.sufficientlyFunded) {
revert IDelivery.SendNotSufficientlyFunded();
}
// parse the deliveryVM payload into the DeliveryInstructions struct
DeliveryInstruction memory deliveryInstruction = container.instructions[targetParams.multisendIndex];
//make sure the specified relayer is the relayer delivering this message
if (fromWormholeFormat(deliveryInstruction.executionParameters.providerDeliveryAddress) != msg.sender) {
revert IDelivery.UnexpectedRelayer();
}
//make sure relayer passed in sufficient funds
if (
msg.value
< deliveryInstruction.maximumRefundTarget + deliveryInstruction.receiverValueTarget + wormholeMessageFee()
) {
revert IDelivery.InsufficientRelayerFunds();
}
//make sure this delivery is intended for this chain
if (chainId() != deliveryInstruction.targetChain) {
revert IDelivery.TargetChainIsNotThisChain(deliveryInstruction.targetChain);
}
_executeDelivery(
deliveryInstruction,
targetParams.encodedVMs,
deliveryVM.hash,
targetParams.relayerRefundAddress,
deliveryVM.emitterChainId,
deliveryVM.sequence
);
}
function toWormholeFormat(address addr) public pure returns (bytes32 whFormat) {
return bytes32(uint256(uint160(addr)));
}
function fromWormholeFormat(bytes32 whFormatAddress) public pure returns (address addr) {
return address(uint160(uint256(whFormatAddress)));
}
function getDefaultRelayParams() public pure returns (bytes memory relayParams) {
return new bytes(0);
}
function quoteGas(uint16 targetChain, uint32 gasLimit, IRelayProvider provider)
public
view
returns (uint256 deliveryQuote)
{
deliveryQuote = provider.quoteDeliveryOverhead(targetChain) + (gasLimit * provider.quoteGasPrice(targetChain));
}
function quoteGasResend(uint16 targetChain, uint32 gasLimit, IRelayProvider provider)
public
view
returns (uint256 redeliveryQuote)
{
redeliveryQuote =
provider.quoteRedeliveryOverhead(targetChain) + (gasLimit * provider.quoteGasPrice(targetChain));
}
//If the integrator pays at least nativeQuote, they should receive at least targetAmount as their application budget
function quoteReceiverValue(uint16 targetChain, uint256 targetAmount, IRelayProvider provider)
public
view
returns (uint256 nativeQuote)
{
(uint16 buffer, uint16 denominator) = provider.getAssetConversionBuffer(targetChain);
nativeQuote = assetConversionHelper(
targetChain, targetAmount, chainId(), uint256(0) + denominator + buffer, denominator, true, provider
);
}
function pay(address payable receiver, uint256 amount) internal returns (bool success) {
if (amount > 0) {
(success,) = receiver.call{value: amount}("");
} else {
success = true;
}
}
}

View File

@ -0,0 +1,330 @@
// contracts/Bridge.sol
// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.0;
import "../interfaces/IWormholeReceiver.sol";
import "../interfaces/IDelivery.sol";
import "./CoreRelayerGovernance.sol";
import "./CoreRelayerStructs.sol";
contract CoreRelayerDelivery is CoreRelayerGovernance {
enum DeliveryStatus {
SUCCESS,
RECEIVER_FAILURE,
FORWARD_REQUEST_FAILURE,
FORWARD_REQUEST_SUCCESS,
INVALID_REDELIVERY
}
event Delivery(
address indexed recipientContract,
uint16 indexed sourceChain,
uint64 indexed sequence,
bytes32 deliveryVaaHash,
DeliveryStatus status
);
function emitForward(uint256 transactionFeeRefundAmount, ForwardInstruction memory forwardInstruction)
internal
returns (bool forwardIsFunded)
{
DeliveryInstructionsContainer memory container = forwardInstruction.container;
//Add any additional funds which were passed in to the refund amount
transactionFeeRefundAmount = transactionFeeRefundAmount + forwardInstruction.msgValue;
//make sure the refund amount covers the native gas amounts
forwardIsFunded = (transactionFeeRefundAmount >= forwardInstruction.totalFee);
container.sufficientlyFunded = forwardIsFunded;
IRelayProvider relayProvider = IRelayProvider(forwardInstruction.relayProvider);
IWormhole wormhole = wormhole();
uint256 wormholeMessageFee = wormhole.messageFee();
if (forwardIsFunded) {
// the rollover chain is the chain in the first request
uint256 amountUnderMaximum = relayProvider.quoteMaximumBudget(container.instructions[0].targetChain)
- (
wormholeMessageFee + container.instructions[0].maximumRefundTarget
+ container.instructions[0].receiverValueTarget
);
uint256 convertedExtraAmount = calculateTargetDeliveryMaximumRefund(
container.instructions[0].targetChain,
transactionFeeRefundAmount - forwardInstruction.totalFee,
relayProvider
);
container.instructions[0].maximumRefundTarget +=
(amountUnderMaximum > convertedExtraAmount) ? convertedExtraAmount : amountUnderMaximum;
}
//emit forwarding instruction
wormhole.publishMessage{value: wormholeMessageFee}(
forwardInstruction.nonce,
encodeDeliveryInstructionsContainer(container),
relayProvider.getConsistencyLevel()
);
// if funded, pay out reward to provider. Otherwise, the delivery code will handle sending a refund.
if (forwardIsFunded) {
pay(relayProvider.getRewardAddress(), transactionFeeRefundAmount);
}
//clear forwarding request from cache
clearForwardInstruction();
}
function _executeDelivery(
DeliveryInstruction memory internalInstruction,
bytes[] memory encodedVMs,
bytes32 deliveryVaaHash,
address payable relayerRefund,
uint16 sourceChain,
uint64 sourceSequence
) internal {
//REVISE Decide whether we want to remove the DeliveryInstructionsContainer from encodedVMs.
// lock the contract to prevent reentrancy
if (isContractLocked()) {
revert IDelivery.ReentrantCall();
}
setContractLock(true);
setLockedTargetAddress(fromWormholeFormat(internalInstruction.targetAddress));
// store gas budget pre target invocation to calculate unused gas budget
uint256 preGas = gasleft();
// call the receiveWormholeMessages endpoint on the target contract
(bool callToTargetContractSucceeded,) = fromWormholeFormat(internalInstruction.targetAddress).call{
gas: internalInstruction.executionParameters.gasLimit,
value: internalInstruction.receiverValueTarget
}(abi.encodeCall(IWormholeReceiver.receiveWormholeMessages, (encodedVMs, new bytes[](0))));
uint256 postGas = gasleft();
// There's no easy way to measure the exact cost of the CALL instruction.
// This is due to the fact that the compiler probably emits DUPN or MSTORE instructions
// to setup the arguments for the call just after our measurement.
// This means the refund could be off by a few units of gas.
// Thus, we ensure the overhead doesn't cause an overflow in our refund formula here.
uint256 gasUsed = (preGas - postGas) > internalInstruction.executionParameters.gasLimit
? internalInstruction.executionParameters.gasLimit
: (preGas - postGas);
// refund unused gas budget
uint256 transactionFeeRefundAmount = (internalInstruction.executionParameters.gasLimit - gasUsed)
* internalInstruction.maximumRefundTarget / internalInstruction.executionParameters.gasLimit;
// unlock the contract
setContractLock(false);
ForwardInstruction memory forwardingRequest = getForwardInstruction();
DeliveryStatus status;
bool forwardIsFunded = false;
if (forwardingRequest.isValid) {
forwardIsFunded = emitForward(transactionFeeRefundAmount, forwardingRequest);
status = forwardIsFunded ? DeliveryStatus.FORWARD_REQUEST_SUCCESS : DeliveryStatus.FORWARD_REQUEST_FAILURE;
} else {
status = callToTargetContractSucceeded ? DeliveryStatus.SUCCESS : DeliveryStatus.RECEIVER_FAILURE;
}
uint256 receiverValueRefundAmount =
(callToTargetContractSucceeded ? 0 : internalInstruction.receiverValueTarget);
uint256 refundToRefundAddress = receiverValueRefundAmount + (forwardIsFunded ? 0 : transactionFeeRefundAmount);
bool refundPaidToRefundAddress =
pay(payable(fromWormholeFormat(internalInstruction.refundAddress)), refundToRefundAddress);
emit Delivery({
recipientContract: fromWormholeFormat(internalInstruction.targetAddress),
sourceChain: sourceChain,
sequence: sourceSequence,
deliveryVaaHash: deliveryVaaHash,
status: status
});
uint256 wormholeMessageFee = wormhole().messageFee();
uint256 extraRelayerFunds = (
msg.value - internalInstruction.receiverValueTarget - internalInstruction.maximumRefundTarget
- wormholeMessageFee
);
uint256 relayerRefundAmount = extraRelayerFunds
+ (internalInstruction.maximumRefundTarget - transactionFeeRefundAmount)
+ (forwardingRequest.isValid ? 0 : wormholeMessageFee) + (refundPaidToRefundAddress ? 0 : refundToRefundAddress);
// refund the rest to relayer
pay(relayerRefund, relayerRefundAmount);
}
function verifyRelayerVM(IWormhole.VM memory vm) internal view returns (bool) {
return registeredCoreRelayerContract(vm.emitterChainId) == vm.emitterAddress;
}
function redeliverSingle(IDelivery.TargetRedeliveryByTxHashParamsSingle memory targetParams) public payable {
//cache wormhole
IWormhole wormhole = wormhole();
//validate the redelivery VM
(IWormhole.VM memory redeliveryVM, bool valid, string memory reason) =
wormhole.parseAndVerifyVM(targetParams.redeliveryVM);
if (!valid) {
revert IDelivery.InvalidRedeliveryVM(reason);
}
if (!verifyRelayerVM(redeliveryVM)) {
// Redelivery VM has an invalid emitter
revert IDelivery.InvalidEmitterInRedeliveryVM();
}
RedeliveryByTxHashInstruction memory redeliveryInstruction = decodeRedeliveryInstruction(redeliveryVM.payload);
//validate the original delivery VM
IWormhole.VM memory originalDeliveryVM;
(originalDeliveryVM, valid, reason) =
wormhole.parseAndVerifyVM(targetParams.sourceEncodedVMs[redeliveryInstruction.deliveryIndex]);
if (!valid) {
revert IDelivery.InvalidVaa(redeliveryInstruction.deliveryIndex, reason);
}
if (!verifyRelayerVM(originalDeliveryVM)) {
// Original Delivery VM has a invalid emitter
revert IDelivery.InvalidEmitterInOriginalDeliveryVM(redeliveryInstruction.deliveryIndex);
}
DeliveryInstruction memory instruction;
(instruction, valid) = validateRedeliverySingle(
redeliveryInstruction,
decodeDeliveryInstructionsContainer(originalDeliveryVM.payload).instructions[redeliveryInstruction
.multisendIndex]
);
if (!valid) {
emit Delivery({
recipientContract: fromWormholeFormat(instruction.targetAddress),
sourceChain: redeliveryVM.emitterChainId,
sequence: redeliveryVM.sequence,
deliveryVaaHash: redeliveryVM.hash,
status: DeliveryStatus.INVALID_REDELIVERY
});
pay(targetParams.relayerRefundAddress, msg.value);
return;
}
_executeDelivery(
instruction,
targetParams.sourceEncodedVMs,
originalDeliveryVM.hash,
targetParams.relayerRefundAddress,
originalDeliveryVM.emitterChainId,
originalDeliveryVM.sequence
);
}
function validateRedeliverySingle(
RedeliveryByTxHashInstruction memory redeliveryInstruction,
DeliveryInstruction memory originalInstruction
) internal view returns (DeliveryInstruction memory deliveryInstruction, bool isValid) {
// All the same checks as delivery single, with a couple additional
// The same relay provider must be specified when doing a single VAA redeliver.
address providerAddress = fromWormholeFormat(redeliveryInstruction.executionParameters.providerDeliveryAddress);
if (providerAddress != fromWormholeFormat(originalInstruction.executionParameters.providerDeliveryAddress)) {
revert IDelivery.MismatchingRelayProvidersInRedelivery();
}
uint256 wormholeMessageFee = wormhole().messageFee();
// relayer must have covered the necessary funds
if (
msg.value
< redeliveryInstruction.newMaximumRefundTarget + redeliveryInstruction.newReceiverValueTarget
+ wormholeMessageFee
) {
revert IDelivery.InsufficientRelayerFunds();
}
uint16 whChainId = chainId();
// msg.sender must be the provider
// "Relay provider differed from the specified address");
isValid = msg.sender == providerAddress
// redelivery must target this chain
// "Redelivery request does not target this chain.");
&& whChainId == redeliveryInstruction.targetChain
// original delivery must target this chain
// "Original delivery request did not target this chain.");
&& whChainId == originalInstruction.targetChain
// gasLimit & receiverValue must be at least as large as the initial delivery
// "New receiver value is smaller than the original"
&& originalInstruction.receiverValueTarget <= redeliveryInstruction.newReceiverValueTarget
// "New gasLimit is smaller than the original"
&& originalInstruction.executionParameters.gasLimit <= redeliveryInstruction.executionParameters.gasLimit;
// Overwrite compute budget and application budget on the original request and proceed.
deliveryInstruction = originalInstruction;
deliveryInstruction.maximumRefundTarget = redeliveryInstruction.newMaximumRefundTarget;
deliveryInstruction.receiverValueTarget = redeliveryInstruction.newReceiverValueTarget;
deliveryInstruction.executionParameters = redeliveryInstruction.executionParameters;
}
function deliverSingle(IDelivery.TargetDeliveryParametersSingle memory targetParams) public payable {
// cache wormhole instance
IWormhole wormhole = wormhole();
// validate the deliveryIndex
(IWormhole.VM memory deliveryVM, bool valid, string memory reason) =
wormhole.parseAndVerifyVM(targetParams.encodedVMs[targetParams.deliveryIndex]);
if (!valid) {
revert IDelivery.InvalidVaa(targetParams.deliveryIndex, reason);
}
if (!verifyRelayerVM(deliveryVM)) {
revert IDelivery.InvalidEmitter();
}
DeliveryInstructionsContainer memory container = decodeDeliveryInstructionsContainer(deliveryVM.payload);
//ensure this is a funded delivery, not a failed forward.
if (!container.sufficientlyFunded) {
revert IDelivery.SendNotSufficientlyFunded();
}
// parse the deliveryVM payload into the DeliveryInstructions struct
DeliveryInstruction memory deliveryInstruction = container.instructions[targetParams.multisendIndex];
//make sure the specified relayer is the relayer delivering this message
if (fromWormholeFormat(deliveryInstruction.executionParameters.providerDeliveryAddress) != msg.sender) {
revert IDelivery.UnexpectedRelayer();
}
uint256 wormholeMessageFee = wormhole.messageFee();
//make sure relayer passed in sufficient funds
if (
msg.value
< deliveryInstruction.maximumRefundTarget + deliveryInstruction.receiverValueTarget + wormholeMessageFee
) {
revert IDelivery.InsufficientRelayerFunds();
}
//make sure this delivery is intended for this chain
if (chainId() != deliveryInstruction.targetChain) {
revert IDelivery.TargetChainIsNotThisChain(deliveryInstruction.targetChain);
}
_executeDelivery(
deliveryInstruction,
targetParams.encodedVMs,
deliveryVM.hash,
targetParams.relayerRefundAddress,
deliveryVM.emitterChainId,
deliveryVM.sequence
);
}
function toWormholeFormat(address addr) public pure returns (bytes32 whFormat) {
return bytes32(uint256(uint160(addr)));
}
function fromWormholeFormat(bytes32 whFormatAddress) public pure returns (address addr) {
return address(uint160(uint256(whFormatAddress)));
}
function pay(address payable receiver, uint256 amount) internal returns (bool success) {
if (amount > 0) {
(success,) = receiver.call{value: amount}("");
} else {
success = true;
}
}
}

View File

@ -33,10 +33,6 @@ contract CoreRelayerGetters is CoreRelayerState {
return IWormhole(_state.provider.wormhole);
}
function wormholeMessageFee() public view returns (uint256) {
return _state.provider.wormholeMessageFee;
}
function chainId() public view returns (uint16) {
return _state.provider.chainId;
}

View File

@ -15,12 +15,17 @@ contract CoreRelayerMessages is CoreRelayerStructs, CoreRelayerGetters {
error InvalidPayloadId(uint8 payloadId);
error InvalidDeliveryInstructionsPayload(uint256 length);
function getTotalFeeMultichainSend(IWormholeRelayer.MultichainSend memory sendContainer)
/**
* @notice This function calculates the total fee to execute all of the Send requests in this MultichainSend container
* @param sendContainer A MultichainSend struct describing all of the Send requests
* @return totalFee
*/
function getTotalFeeMultichainSend(IWormholeRelayer.MultichainSend memory sendContainer, uint256 wormholeMessageFee)
internal
view
returns (uint256 totalFee)
{
totalFee = wormholeMessageFee();
totalFee = wormholeMessageFee;
uint256 length = sendContainer.requests.length;
for (uint256 i = 0; i < length; i++) {
IWormholeRelayer.Send memory request = sendContainer.requests[i];
@ -28,6 +33,20 @@ contract CoreRelayerMessages is CoreRelayerStructs, CoreRelayerGetters {
}
}
/**
* @notice This function converts a MultichainSend struct into a DeliveryInstructionsContainer struct that
* describes to the relayer exactly how to relay for each of the Send requests.
* Specifically, each Send is converted to a DeliveryInstruction, which is a struct that contains six fields:
* 1) targetChain, 2) targetAddress, 3) refundAddress (all which are part of the Send struct),
* 4) maximumRefundTarget: The maximum amount that can be refunded to 'refundAddress' (e.g. if the call to 'receiveWormholeMessages' takes 0 gas),
* 5) receiverValueTarget: The amount that will be passed into 'receiveWormholeMessages' as value, in target chain currency
* 6) executionParameters: a struct with information about execution, specifically:
* executionParameters.gasLimit: The maximum amount of gas 'receiveWormholeMessages' is allowed to use
* executionParameters.providerDeliveryAddress: The address of the relayer that will execute this Send request
* The latter 3 fields are calculated using the relayProvider's getters
* @param sendContainer A MultichainSend struct describing all of the Send requests
* @return instructionsContainer A DeliveryInstructionsContainer struct
*/
function convertMultichainSendToDeliveryInstructionsContainer(IWormholeRelayer.MultichainSend memory sendContainer)
internal
view
@ -43,6 +62,21 @@ contract CoreRelayerMessages is CoreRelayerStructs, CoreRelayerGetters {
}
}
/**
* @notice This function converts a Send struct into a DeliveryInstruction struct that
* describes to the relayer exactly how to relay for the Send.
* Specifically, the DeliveryInstruction struct that contains six fields:
* 1) targetChain, 2) targetAddress, 3) refundAddress (all which are part of the Send struct),
* 4) maximumRefundTarget: The maximum amount that can be refunded to 'refundAddress' (e.g. if the call to 'receiveWormholeMessages' takes 0 gas),
* 5) receiverValueTarget: The amount that will be passed into 'receiveWormholeMessages' as value, in target chain currency
* 6) executionParameters: a struct with information about execution, specifically:
* executionParameters.gasLimit: The maximum amount of gas 'receiveWormholeMessages' is allowed to use
* executionParameters.providerDeliveryAddress: The address of the relayer that will execute this Send request
* The latter 3 fields are calculated using the relayProvider's getters
* @param send A Send struct
* @param relayProvider The relay provider chosen for this Send
* @return instruction A DeliveryInstruction
*/
function convertSendToDeliveryInstruction(IWormholeRelayer.Send memory send, IRelayProvider relayProvider)
internal
view
@ -62,6 +96,14 @@ contract CoreRelayerMessages is CoreRelayerStructs, CoreRelayerGetters {
});
}
/**
* @notice Check if for each instruction in the DeliveryInstructionContainer,
* - the total amount of target chain currency needed for execution of the instruction is within the maximum budget,
* i.e. (maximumRefundTarget + receiverValueTarget) <= (the relayProvider's maximum budget for the target chain)
* - the gasLimit is greater than 0
* @param container A DeliveryInstructionsContainer
* @param relayProvider The relayProvider whos maximum budget we are checking against
*/
function checkInstructions(DeliveryInstructionsContainer memory container, IRelayProvider relayProvider)
internal
view
@ -73,7 +115,7 @@ contract CoreRelayerMessages is CoreRelayerStructs, CoreRelayerGetters {
revert IWormholeRelayer.MaxTransactionFeeNotEnough(i);
}
if (
instruction.maximumRefundTarget + instruction.receiverValueTarget + wormholeMessageFee()
instruction.maximumRefundTarget + instruction.receiverValueTarget
> relayProvider.quoteMaximumBudget(instruction.targetChain)
) {
revert IWormholeRelayer.FundsTooMuch(i);
@ -81,44 +123,68 @@ contract CoreRelayerMessages is CoreRelayerStructs, CoreRelayerGetters {
}
}
function checkRedeliveryInstruction(RedeliveryByTxHashInstruction memory instruction, IRelayProvider relayProvider)
internal
view
{
/**
* @notice Check if for a redelivery instruction,
* - the total amount of target chain currency needed for execution of this instruction is within the maximum budget,
* i.e. (maximumRefundTarget + receiverValueTarget) <= (the relayProvider's maximum budget for the target chain)
* - the gasLimit is greater than 0
* @param instruction A RedeliveryByTxHashInstruction
* @param relayProvider The relayProvider whos maximum budget we are checking against
*/
function checkRedeliveryInstruction(
RedeliveryByTxHashInstruction memory instruction,
IRelayProvider relayProvider,
uint256 wormholeMessageFee
) internal view {
if (instruction.executionParameters.gasLimit == 0) {
revert IWormholeRelayer.MaxTransactionFeeNotEnough(0);
}
if (
instruction.newMaximumRefundTarget + instruction.newReceiverValueTarget + wormholeMessageFee()
instruction.newMaximumRefundTarget + instruction.newReceiverValueTarget + wormholeMessageFee
> relayProvider.quoteMaximumBudget(instruction.targetChain)
) {
revert IWormholeRelayer.FundsTooMuch(0);
}
}
function convertResendToRedeliveryInstruction(IWormholeRelayer.ResendByTx memory send, IRelayProvider relayProvider)
internal
view
returns (RedeliveryByTxHashInstruction memory instruction)
{
/**
* @notice This function converts a ResendByTx struct into a RedeliveryByTxHashInstruction struct that
* describes to the relayer exactly how to relay for the ResendByTx.
* Specifically, the RedeliveryByTxHashInstruction struct that contains nine fields:
* 1) sourceChain, 2) sourceTxHash, 3) sourceNonce, 4) targetChain, 5) deliveryIndex, 6) multisendIndex (all which are part of the ResendByTxHash struct),
* 7) newMaximumRefundTarget: The new maximum amount that can be refunded to 'refundAddress' (e.g. if the call to 'receiveWormholeMessages' takes 0 gas),
* 8) newReceiverValueTarget: The new amount that will be passed into 'receiveWormholeMessages' as value, in target chain currency
* 9) executionParameters: a struct with information about execution, specifically:
* executionParameters.gasLimit: The maximum amount of gas 'receiveWormholeMessages' is allowed to use
* executionParameters.providerDeliveryAddress: The address of the relayer that will execute this ResendByTx request
* The latter 3 fields are calculated using the relayProvider's getters
* @param resend A ResendByTx struct
* @param relayProvider The relay provider chosen for this ResendByTx
* @return instruction A DeliveryInstruction
*/
function convertResendToRedeliveryInstruction(
IWormholeRelayer.ResendByTx memory resend,
IRelayProvider relayProvider
) internal view returns (RedeliveryByTxHashInstruction memory instruction) {
instruction.payloadId = 2;
instruction.sourceChain = send.sourceChain;
instruction.sourceTxHash = send.sourceTxHash;
instruction.sourceNonce = send.sourceNonce;
instruction.targetChain = send.targetChain;
instruction.deliveryIndex = send.deliveryIndex;
instruction.multisendIndex = send.multisendIndex;
instruction.sourceChain = resend.sourceChain;
instruction.sourceTxHash = resend.sourceTxHash;
instruction.sourceNonce = resend.sourceNonce;
instruction.targetChain = resend.targetChain;
instruction.deliveryIndex = resend.deliveryIndex;
instruction.multisendIndex = resend.multisendIndex;
instruction.newMaximumRefundTarget =
calculateTargetRedeliveryMaximumRefund(send.targetChain, send.newMaxTransactionFee, relayProvider);
calculateTargetRedeliveryMaximumRefund(resend.targetChain, resend.newMaxTransactionFee, relayProvider);
instruction.newReceiverValueTarget =
convertReceiverValueAmount(send.newReceiverValue, send.targetChain, relayProvider);
convertReceiverValueAmount(resend.newReceiverValue, resend.targetChain, relayProvider);
instruction.executionParameters = ExecutionParameters({
version: 1,
gasLimit: calculateTargetGasRedeliveryAmount(send.targetChain, send.newMaxTransactionFee, relayProvider),
providerDeliveryAddress: relayProvider.getDeliveryAddress(send.targetChain)
gasLimit: calculateTargetGasRedeliveryAmount(resend.targetChain, resend.newMaxTransactionFee, relayProvider),
providerDeliveryAddress: relayProvider.getDeliveryAddress(resend.targetChain)
});
}
// encode a 'RedeliveryByTxHashInstruction' into bytes
function encodeRedeliveryInstruction(RedeliveryByTxHashInstruction memory instruction)
internal
pure
@ -140,6 +206,7 @@ contract CoreRelayerMessages is CoreRelayerStructs, CoreRelayerGetters {
);
}
// encode a 'DeliveryInstructionsContainer' into bytes
function encodeDeliveryInstructionsContainer(DeliveryInstructionsContainer memory container)
internal
pure
@ -154,6 +221,7 @@ contract CoreRelayerMessages is CoreRelayerStructs, CoreRelayerGetters {
}
}
// encode a 'DeliveryInstruction' into bytes
function encodeDeliveryInstruction(DeliveryInstruction memory instruction)
internal
pure
@ -173,7 +241,16 @@ contract CoreRelayerMessages is CoreRelayerStructs, CoreRelayerGetters {
/**
* Given a targetChain, maxTransactionFee, and a relay provider, this function calculates what the gas limit of the delivery transaction
* should be.
* should be
*
* It does this by calculating (maxTransactionFee - deliveryOverhead)/gasPrice
* where 'deliveryOverhead' is the relayProvider's base fee for delivering to targetChain (in units of source chain currency)
* and 'gasPrice' is the relayProvider's fee per unit of target chain gas (in units of source chain currency)
*
* @param targetChain target chain
* @param maxTransactionFee uint256
* @param provider IRelayProvider
* @return gasAmount
*/
function calculateTargetGasDeliveryAmount(uint16 targetChain, uint256 maxTransactionFee, IRelayProvider provider)
internal
@ -185,6 +262,20 @@ contract CoreRelayerMessages is CoreRelayerStructs, CoreRelayerGetters {
);
}
/**
* Given a targetChain, maxTransactionFee, and a relay provider, this function calculates what the maximum refund of the delivery transaction
* should be, in terms of target chain currency
*
* The maximum refund is the amount that would be refunded to refundAddress if the call to 'receiveWormholeMessages' takes 0 gas
*
* It does this by calculating (maxTransactionFee - deliveryOverhead) and converting (using the relay provider's prices) to target chain currency
* (where 'deliveryOverhead' is the relayProvider's base fee for delivering to targetChain [in units of source chain currency])
*
* @param targetChain target chain
* @param maxTransactionFee uint256
* @param provider IRelayProvider
* @return maximumRefund uint256
*/
function calculateTargetDeliveryMaximumRefund(
uint16 targetChain,
uint256 maxTransactionFee,
@ -197,7 +288,16 @@ contract CoreRelayerMessages is CoreRelayerStructs, CoreRelayerGetters {
/**
* Given a targetChain, maxTransactionFee, and a relay provider, this function calculates what the gas limit of the redelivery transaction
* should be.
* should be
*
* It does this by calculating (maxTransactionFee - redeliveryOverhead)/gasPrice
* where 'redeliveryOverhead' is the relayProvider's base fee for redelivering to targetChain (in units of source chain currency)
* and 'gasPrice' is the relayProvider's fee per unit of target chain gas (in units of source chain currency)
*
* @param targetChain target chain
* @param maxTransactionFee uint256
* @param provider IRelayProvider
* @return gasAmount
*/
function calculateTargetGasRedeliveryAmount(uint16 targetChain, uint256 maxTransactionFee, IRelayProvider provider)
internal
@ -209,6 +309,20 @@ contract CoreRelayerMessages is CoreRelayerStructs, CoreRelayerGetters {
);
}
/**
* Given a targetChain, maxTransactionFee, and a relay provider, this function calculates what the maximum refund of the redelivery transaction
* should be, in terms of target chain currency
*
* The maximum refund is the amount that would be refunded to refundAddress if the call to 'receiveWormholeMessages' takes 0 gas
*
* It does this by calculating (maxTransactionFee - redeliveryOverhead) and converting (using the relay provider's prices) to target chain currency
* (where 'redeliveryOverhead' is the relayProvider's base fee for redelivering to targetChain [in units of source chain currency])
*
* @param targetChain target chain
* @param maxTransactionFee uint256
* @param provider IRelayProvider
* @return maximumRefund uint256
*/
function calculateTargetRedeliveryMaximumRefund(
uint16 targetChain,
uint256 maxTransactionFee,
@ -219,16 +333,25 @@ contract CoreRelayerMessages is CoreRelayerStructs, CoreRelayerGetters {
);
}
/**
* Performs the calculation (maxTransactionFee - overhead)/(price of 1 unit of target chain gas, in source chain currency)
* and bounds the result between 0 and 2^32-1, inclusive
*
* @param targetChain uint16
* @param maxTransactionFee uint256
* @param overhead uint256
* @param provider IRelayProvider
*/
function calculateTargetGasDeliveryAmountHelper(
uint16 targetChain,
uint256 maxTransactionFee,
uint256 deliveryOverhead,
uint256 overhead,
IRelayProvider provider
) internal view returns (uint32 gasAmount) {
if (maxTransactionFee <= deliveryOverhead) {
if (maxTransactionFee <= overhead) {
gasAmount = 0;
} else {
uint256 gas = (maxTransactionFee - deliveryOverhead) / provider.quoteGasPrice(targetChain);
uint256 gas = (maxTransactionFee - overhead) / provider.quoteGasPrice(targetChain);
if (gas > type(uint32).max) {
gasAmount = type(uint32).max;
} else {
@ -237,20 +360,44 @@ contract CoreRelayerMessages is CoreRelayerStructs, CoreRelayerGetters {
}
}
/**
* Converts (maxTransactionFee - overhead) from source to target chain currency, using the provider's prices
*
* @param targetChain uint16
* @param maxTransactionFee uint256
* @param overhead uint256
* @param provider IRelayProvider
*/
function calculateTargetDeliveryMaximumRefundHelper(
uint16 targetChain,
uint256 maxTransactionFee,
uint256 deliveryOverhead,
uint256 overhead,
IRelayProvider provider
) internal view returns (uint256 maximumRefund) {
if (maxTransactionFee >= deliveryOverhead) {
uint256 remainder = maxTransactionFee - deliveryOverhead;
if (maxTransactionFee >= overhead) {
uint256 remainder = maxTransactionFee - overhead;
maximumRefund = assetConversionHelper(chainId(), remainder, targetChain, 1, 1, false, provider);
} else {
maximumRefund = 0;
}
}
/**
* Converts 'sourceAmount' of source chain currency to units of target chain currency
* using the prices of 'provider'
* and also multiplying by a specified fraction 'multiplier/multiplierDenominator',
* rounding up or down specified by 'roundUp', and without performing intermediate rounding,
* i.e. the result should be as if float arithmetic was done and the rounding performed at the end
*
* @param sourceChain source chain
* @param sourceAmount amount of source chain currency to be converted
* @param targetChain target chain
* @param multiplier numerator of a fraction to multiply by
* @param multiplierDenominator denominator of a fraction to multiply by
* @param roundUp whether or not to round up
* @param provider relay provider
* @return targetAmount amount of target chain currency
*/
function assetConversionHelper(
uint16 sourceChain,
uint256 sourceAmount,
@ -278,8 +425,18 @@ contract CoreRelayerMessages is CoreRelayerStructs, CoreRelayerGetters {
}
}
//This should invert quoteApplicationBudgetAmount, I.E when a user pays the sourceAmount, they receive at least the value of targetAmount they requested from
//quoteReceiverValue.
/**
* If the user specifies (for 'receiverValue) 'sourceAmount' of source chain currency, with relay provider 'provider',
* then this function calculates how much the relayer will pass into receiveWormholeMessages on the target chain (in target chain currency)
*
* The calculation simply converts this amount to target chain currency, but also applies a multiplier of 'denominator/(denominator + buffer)'
* where these values are also specified by the relay provider 'provider'
*
* @param sourceAmount amount of source chain currency
* @param targetChain target chain
* @param provider relay provider
* @return targetAmount amount of target chain currency
*/
function convertReceiverValueAmount(uint256 sourceAmount, uint16 targetChain, IRelayProvider provider)
internal
view
@ -292,6 +449,7 @@ contract CoreRelayerMessages is CoreRelayerStructs, CoreRelayerGetters {
);
}
// decode a 'RedeliveryByTxHashInstruction' from bytes
function decodeRedeliveryInstruction(bytes memory encoded)
public
pure
@ -339,6 +497,7 @@ contract CoreRelayerMessages is CoreRelayerStructs, CoreRelayerGetters {
index += 32;
}
// decode a 'DeliveryInstructionsContainer' from bytes
function decodeDeliveryInstructionsContainer(bytes memory encoded)
public
pure

View File

@ -35,10 +35,6 @@ contract CoreRelayerSetters is CoreRelayerState, Context {
_state.provider.wormhole = payable(wh);
}
function updateWormholeMessageFee() internal {
_state.provider.wormholeMessageFee = IWormhole(_state.provider.wormhole).messageFee();
}
function setRelayProvider(address defaultRelayProvider) internal {
_state.defaultRelayProvider = defaultRelayProvider;
}

View File

@ -9,7 +9,6 @@ contract CoreRelayerStorage {
struct Provider {
uint16 chainId;
address payable wormhole;
uint256 wormholeMessageFee;
uint16 governanceChainId;
bytes32 governanceContract;
}

View File

@ -170,6 +170,55 @@ interface IWormholeRelayer {
*/
function forward(Send memory request, uint32 nonce, address relayProvider) external payable;
/**
* @notice This 'MultichainSend' struct represents a collection of send requests 'requests' and a specified relay provider 'relayProviderAddress'
* This struct is used to request sending the same array of transactions to many different destination contracts (on many different chains),
* With each request in the 'requests' array denoting parameters for one specific destination
*
* @custom:member relayProviderAddress The address of the relay provider's contract on this source chain. This relay provider will perform delivery of all of these Send requests.
* Use getDefaultRelayProvider() here to use the default relay provider.
* @custom:member requests The array of send requests, each specifying a targetAddress on a targetChain, along with information about the desired maxTransactionFee, receiverValue, refundAddress, and relayParameters
*/
struct MultichainSend {
address relayProviderAddress;
Send[] requests;
}
/**
* @notice The multichainSend function delivers all wormhole messages in the current transaction of nonce 'nonce' to many destinations,
* with each destination specified in a Send struct, describing the desired targetAddress, targetChain, maxTransactionFee, receiverValue, refundAddress, and relayParameters
*
* @param sendContainer The MultichainSend struct, containing the array of Send requests, as well as the desired relayProviderAddress
* @param nonce The messages to be relayed are all of the emitted wormhole messages in the current transaction that have nonce 'nonce'
*
* This function must be called with a payment of at least (one wormhole message fee) + Sum_(i=0 -> sendContainer.requests.length - 1) [sendContainer.requests[i].maxTransactionFee + sendContainer.requests[i].receiverValue].
*
* @return sequence The sequence number for the emitted wormhole message, which contains encoded delivery instructions meant for the default wormhole relay provider.
* The relay provider will listen for these messages, and then execute the delivery as described
*/
function multichainSend(MultichainSend memory sendContainer, uint32 nonce)
external
payable
returns (uint64 sequence);
/**
* @notice The multichainForward function can only be called in a IWormholeReceiver within the 'receiveWormholeMessages' function
* It's purpose is to use any leftover fee from the 'maxTransactionFee' of the current delivery to fund another delivery, specifically a multichain delivery to many destinations
* See the description of 'forward' for further explanation of what a forward is.
* multichainForward provides the same functionality of forward, while additionally allowing the same array of wormhole messages to be sent to many destinations
*
* Let LEFTOVER_VALUE = (leftover funds from the current delivery that would have been refunded) + (any extra msg.value passed into forward)
* and let NEEDED_VALUE = (one wormhole message fee) + Sum_(i=0 -> requests.requests.length - 1) [requests.requests[i].maxTransactionFee + requests.requests[i].receiverValue].
* The multichainForward will succeed if LEFTOVER_VALUE >= NEEDED_VALUE
*
* note: If LEFTOVER_VALUE > NEEDED_VALUE, then the maxTransactionFee of the first request in the array of sends will be incremented by 'LEFTOVER_VALUE - NEEDED_VALUE'
*
* @param requests The MultichainSend struct, containing the array of Send requests, as well as the desired relayProviderAddress
* @param nonce The messages to be relayed are all of the emitted wormhole messages in the current transaction that have nonce 'nonce'
*
*/
function multichainForward(MultichainSend memory requests, uint32 nonce) external payable;
/**
* @notice This 'ResendByTx' struct represents a request to resend an array of messages that have been previously requested to be sent
* Specifically, if a user in transaction 'txHash' on chain 'sourceChain' emits many wormhole messages of nonce 'sourceNonce' and then
@ -207,10 +256,10 @@ interface IWormholeRelayer {
}
/**
* @notice This 'ResendByTx' struct represents a request to resend an array of messages that have been previously requested to be sent
* @notice This 'resend' function emits a wormhole message requesting to resend an array of messages that have been previously requested to be sent
* Specifically, if a user in transaction 'txHash' on chain 'sourceChain' emits many wormhole messages of nonce 'sourceNonce' and then
* makes a call to 'send' requesting these messages to be sent to 'targetAddress' on 'targetChain',
* then the user can request a redelivery of these wormhole messages any time in the future through a call to 'resend' using this struct
* then the user can request a redelivery of these wormhole messages any time in the future through a call to 'resend' using this function
*
* @param request Information about the resend request, including the source chain and source transaction hash,
* @param relayProvider The address of (the relay provider you wish to deliver the messages)'s contract on this source chain. This must be a contract that implements IRelayProvider.
@ -221,52 +270,6 @@ interface IWormholeRelayer {
*/
function resend(ResendByTx memory request, address relayProvider) external payable returns (uint64 sequence);
/**
* @notice This 'MultichainSend' struct represents a collection of send requests 'requests' and a specified relay provider 'relayProviderAddress'
* This struct is used to request sending the same array of transactions to many different destination contracts (on many different chains),
* With each request in the 'requests' array denoting parameters for one specific destination
*
* @custom:member relayProviderAddress The address of the relay provider's contract on this source chain. This relay provider will perform delivery of all of these Send requests.
* Use getDefaultRelayProvider() here to use the default relay provider.
* @custom:member requests The array of send requests, each specifying a targetAddress on a targetChain, along with information about the desired maxTransactionFee, receiverValue, refundAddress, and relayParameters
*/
struct MultichainSend {
address relayProviderAddress;
Send[] requests;
}
/**
* @notice The multichainSend function delivers all wormhole messages in the current transaction of nonce 'nonce' to many destinations,
* with each destination specified in a Send struct, describing the desired targetAddress, targetChain, maxTransactionFee, receiverValue, refundAddress, and relayParameters
*
* @param requests The MultichainSend struct, containing the array of Send requests, as well as the desired relayProviderAddress
* @param nonce The messages to be relayed are all of the emitted wormhole messages in the current transaction that have nonce 'nonce'
*
* This function must be called with a payment of at least (one wormhole message fee) + Sum_(i=0 -> requests.requests.length - 1) [requests.requests[i].maxTransactionFee + requests.requests[i].receiverValue].
*
* @return sequence The sequence number for the emitted wormhole message, which contains encoded delivery instructions meant for the default wormhole relay provider.
* The relay provider will listen for these messages, and then execute the delivery as described
*/
function multichainSend(MultichainSend memory requests, uint32 nonce) external payable returns (uint64 sequence);
/**
* @notice The multichainForward function can only be called in a IWormholeReceiver within the 'receiveWormholeMessages' function
* It's purpose is to use any leftover fee from the 'maxTransactionFee' of the current delivery to fund another delivery, specifically a multichain delivery to many destinations
* See the description of 'forward' for further explanation of what a forward is.
* multichainForward provides the same functionality of forward, while additionally allowing the same array of wormhole messages to be sent to many destinations
*
* Let LEFTOVER_VALUE = (leftover funds from the current delivery that would have been refunded) + (any extra msg.value passed into forward)
* and let NEEDED_VALUE = (one wormhole message fee) + Sum_(i=0 -> requests.requests.length - 1) [requests.requests[i].maxTransactionFee + requests.requests[i].receiverValue].
* The multichainForward will succeed if LEFTOVER_VALUE >= NEEDED_VALUE
*
* note: If LEFTOVER_VALUE > NEEDED_VALUE, then the maxTransactionFee of the first request in the array of sends will be incremented by 'LEFTOVER_VALUE - NEEDED_VALUE'
*
* @param requests The MultichainSend struct, containing the array of Send requests, as well as the desired relayProviderAddress
* @param nonce The messages to be relayed are all of the emitted wormhole messages in the current transaction that have nonce 'nonce'
*
*/
function multichainForward(MultichainSend memory requests, uint32 nonce) external payable;
/**
* @notice quoteGas tells you how much maxTransactionFee (denominated in current (source) chain currency) must be in order to fund a call to
* receiveWormholeMessages on a contract on chain 'targetChain' that uses 'gasLimit' units of gas
@ -307,7 +310,7 @@ interface IWormholeRelayer {
/**
* @notice quoteReceiverValue tells you how much receiverValue (denominated in current (source) chain currency) must be
* in order for the relayer to pass in 'targetAmount' as msg value when calling receiveWormholeMessages.
* in order for the relay provider to pass in 'targetAmount' as msg value when calling receiveWormholeMessages.
*
* Specifically, for a send 'request',
* In order for 'request.targetAddress''s receiveWormholeMessage function to be called with 'targetAmount' of value,