diff --git a/ethereum/contracts/coreRelayer/CoreRelayerDelivery.sol b/ethereum/contracts/coreRelayer/CoreRelayerDelivery.sol index b3de199..1e61369 100644 --- a/ethereum/contracts/coreRelayer/CoreRelayerDelivery.sol +++ b/ethereum/contracts/coreRelayer/CoreRelayerDelivery.sol @@ -157,56 +157,108 @@ contract CoreRelayerDelivery is CoreRelayerGovernance { return registeredCoreRelayerContract(vm.emitterChainId) == vm.emitterAddress; } + /** + * @notice The relay provider calls 'redeliverSingle' to relay messages as described by one redelivery instruction + * + * The instruction specifies, among other things, the target chain (must be this chain), refund address, new maximum refund (in this chain's currency), + * new receiverValue (in this chain's currency), new upper bound on gas + * + * The relay provider must pass in the original signed wormhole messages from the source chain of the same nonce + * (the wormhole message with the original delivery instructions (the delivery VAA) must be one of these messages) + * as well as the wormhole message with the new redelivery instruction (the redelivery VAA) + * + * The messages will be relayed to the target address (with the specified gas limit and receiver value) iff the following checks are met: + * - the redelivery VAA (targetParams.redeliveryVM) has a valid signature + * - the redelivery VAA's emitter is one of these CoreRelayer contracts + * - the original delivery VAA has a valid signature + * - the original delivery VAA's emitter is one of these CoreRelayer contracts + * - the new redelivery instruction's upper bound on gas >= the original instruction's upper bound on gas + * - the new redelivery instruction's 'receiver value' amount >= the original instruction's 'receiver value' amount + * - the redelivery instruction's target chain = this chain + * - the original instruction's target chain = this chain + * - for the redelivery instruction, the relay provider passed in at least [(one wormhole message fee) + instruction.newMaximumRefundTarget + instruction.newReceiverValueTarget] of this chain's currency as msg.value + * - msg.sender is the permissioned address allowed to execute this redelivery instruction + * - the permissioned address allowed to execute this redelivery instruction is the permissioned address allowed to execute the old instruction + * + * @param targetParams struct containing the signed wormhole messages and encoded redelivery instruction (and other information) + */ 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); + + // Check that the redelivery VAA (targetParams.redeliveryVM) has a valid signature if (!valid) { revert IDelivery.InvalidRedeliveryVM(reason); } + + // Check that the redelivery VAA's emitter is one of these CoreRelayer contracts if (!verifyRelayerVM(redeliveryVM)) { - // Redelivery VM has an invalid emitter revert IDelivery.InvalidEmitterInRedeliveryVM(); } RedeliveryByTxHashInstruction memory redeliveryInstruction = decodeRedeliveryInstruction(redeliveryVM.payload); - //validate the original delivery VM + // Obtain the original delivery VAA IWormhole.VM memory originalDeliveryVM; (originalDeliveryVM, valid, reason) = wormhole.parseAndVerifyVM(targetParams.sourceEncodedVMs[redeliveryInstruction.deliveryIndex]); + + // Check that the original delivery VAA has a valid signature if (!valid) { revert IDelivery.InvalidVaa(redeliveryInstruction.deliveryIndex, reason); } + + // Check that the original delivery VAA's emitter is one of these CoreRelayer contracts 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] + // Obtain the specific old instruction that was originally executed (and is meant to be re-executed with new parameters) + // specifying the the target chain (must be this chain), target address, refund address, old maximum refund (in this chain's currency), + // old receiverValue (in this chain's currency), old upper bound on gas, and the permissioned address allowed to execute this instruction + DeliveryInstruction memory originalInstruction = decodeDeliveryInstructionsContainer(originalDeliveryVM.payload).instructions[redeliveryInstruction + .multisendIndex]; + + // Perform the following checks: + // - the new redelivery instruction's upper bound on gas >= the original instruction's upper bound on gas + // - the new redelivery instruction's 'receiver value' amount >= the original instruction's 'receiver value' amount + // - the redelivery instruction's target chain = this chain + // - the original instruction's target chain = this chain + // - for the redelivery instruction, the relay provider passed in at least [(one wormhole message fee) + instruction.newMaximumRefundTarget + instruction.newReceiverValueTarget] of this chain's currency as msg.value + // - msg.sender is the permissioned address allowed to execute this redelivery instruction + // - the permissioned address allowed to execute this redelivery instruction is the permissioned address allowed to execute the old instruction + valid = checkRedeliveryInstructionTarget( + redeliveryInstruction, originalInstruction ); + // Emit an 'Invalid Redelivery' event if one of the following five checks failed: + // - msg.sender is the permissioned address allowed to execute this redelivery instruction + // - the redelivery instruction's target chain = this chain + // - the original instruction's target chain = this chain + // - the new redelivery instruction's 'receiver value' amount >= the original instruction's 'receiver value' amount + // - the new redelivery instruction's upper bound on gas >= the original instruction's upper bound on gas if (!valid) { emit Delivery({ - recipientContract: fromWormholeFormat(instruction.targetAddress), - sourceChain: redeliveryVM.emitterChainId, - sequence: redeliveryVM.sequence, - deliveryVaaHash: redeliveryVM.hash, + recipientContract: fromWormholeFormat(originalInstruction.targetAddress), + sourceChain: originalDeliveryVM.emitterChainId, + sequence: originalDeliveryVM.sequence, + deliveryVaaHash: originalDeliveryVM.hash, status: DeliveryStatus.INVALID_REDELIVERY }); pay(targetParams.relayerRefundAddress, msg.value); return; } + // Replace maximumRefund, receiverValue, and the gasLimit on the original request + originalInstruction.maximumRefundTarget = redeliveryInstruction.newMaximumRefundTarget; + originalInstruction.receiverValueTarget = redeliveryInstruction.newReceiverValueTarget; + originalInstruction.executionParameters = redeliveryInstruction.executionParameters; + _executeDelivery( - instruction, + originalInstruction, targetParams.sourceEncodedVMs, originalDeliveryVM.hash, targetParams.relayerRefundAddress, @@ -215,20 +267,33 @@ contract CoreRelayerDelivery is CoreRelayerGovernance { ); } - function validateRedeliverySingle( + /** + * Check that: + * - the new redelivery instruction's upper bound on gas >= the original instruction's upper bound on gas + * - the new redelivery instruction's 'receiver value' amount >= the original instruction's 'receiver value' amount + * - the redelivery instruction's target chain = this chain + * - the original instruction's target chain = this chain + * - for the redelivery instruction, the relay provider passed in at least [(one wormhole message fee) + instruction.newMaximumRefundTarget + instruction.newReceiverValueTarget] of this chain's currency as msg.value + * - msg.sender is the permissioned address allowed to execute this redelivery instruction + * - the permissioned address allowed to execute this redelivery instruction is the permissioned address allowed to execute the old instruction + * @param redeliveryInstruction redelivery instruction + * @param originalInstruction old instruction + */ + function checkRedeliveryInstructionTarget( 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 + ) internal view returns (bool isValid) { - // 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)) { + + // Check that the permissioned address allowed to execute this redelivery instruction is the permissioned address allowed to execute the old instruction + if ((providerAddress != fromWormholeFormat(originalInstruction.executionParameters.providerDeliveryAddress))) { revert IDelivery.MismatchingRelayProvidersInRedelivery(); } uint256 wormholeMessageFee = wormhole().messageFee(); - // relayer must have covered the necessary funds + + // Check that for the redelivery instruction, the relay provider passed in at least [(one wormhole message fee) + instruction.newMaximumRefundTarget + instruction.newReceiverValueTarget] of this chain's currency as msg.value if ( msg.value < redeliveryInstruction.newMaximumRefundTarget + redeliveryInstruction.newReceiverValueTarget @@ -238,57 +303,64 @@ contract CoreRelayerDelivery is CoreRelayerGovernance { } uint16 whChainId = chainId(); - // msg.sender must be the provider - // "Relay provider differed from the specified address"); + + // Check that msg.sender is the permissioned address allowed to execute this redelivery instruction isValid = msg.sender == providerAddress - // redelivery must target this chain - // "Redelivery request does not target this chain."); + + // Check that the redelivery instruction's target chain = this chain && whChainId == redeliveryInstruction.targetChain - // original delivery must target this chain - // "Original delivery request did not target this chain."); + + // Check that the original instruction's target chain = 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" + + // Check that the new redelivery instruction's 'receiver value' amount >= the original instruction's 'receiver value' amount && originalInstruction.receiverValueTarget <= redeliveryInstruction.newReceiverValueTarget - // "New gasLimit is smaller than the original" + + // Check that the new redelivery instruction's upper bound on gas >= the original instruction's upper bound on gas && 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; } /** * @notice The relay provider calls 'deliverSingle' to relay messages as described by one delivery instruction * * The instruction specifies the target chain (must be this chain), target address, refund address, maximum refund (in this chain's currency), - * receiverValue (in this chain's currency), upper bound on gas, and the permissioned address allowed to execute this instruction + * receiver value (in this chain's currency), upper bound on gas, and the permissioned address allowed to execute this instruction * - * The relay provider must pass in the signed wormhole messages from the source chain of the same nonce + * The relay provider must pass in the signed wormhole messages (VAAs) from the source chain of the same nonce * (the wormhole message with the delivery instructions (the delivery VAA) must be one of these messages) * as well as identify which of these messages is the delivery VAA and which of the many instructions in the multichainSend container is meant to be executed * + * The messages will be relayed to the target address (with the specified gas limit and receiver value) iff the following checks are met: + * - the delivery VAA has a valid signature + * - the delivery VAA's emitter is one of these CoreRelayer contracts + * - the delivery instruction container in the delivery VAA was fully funded + * - the instruction's target chain is this chain + * - the relay provider passed in at least [(one wormhole message fee) + instruction.maximumRefundTarget + instruction.receiverValueTarget] of this chain's currency as msg.value + * - msg.sender is the permissioned address allowed to execute this instruction + * * @param targetParams struct containing the signed wormhole messages and encoded delivery instruction container (and other information) */ function deliverSingle(IDelivery.TargetDeliveryParametersSingle memory targetParams) public payable { IWormhole wormhole = wormhole(); - // Verify the signature and emitter of the wormhole message containing the delivery instructions container + // Obtain the delivery VAA (IWormhole.VM memory deliveryVM, bool valid, string memory reason) = wormhole.parseAndVerifyVM(targetParams.encodedVMs[targetParams.deliveryIndex]); + + // Check that the delivery VAA has a valid signature if (!valid) { revert IDelivery.InvalidVaa(targetParams.deliveryIndex, reason); } + + // Check that the delivery VAA's emitter is one of these CoreRelayer contracts if (!verifyRelayerVM(deliveryVM)) { revert IDelivery.InvalidEmitter(); } DeliveryInstructionsContainer memory container = decodeDeliveryInstructionsContainer(deliveryVM.payload); - // Check that the delivery instructions container was fully funded + // Check that the delivery instruction container in the delivery VAA was fully funded if (!container.sufficientlyFunded) { revert IDelivery.SendNotSufficientlyFunded(); } @@ -298,14 +370,14 @@ contract CoreRelayerDelivery is CoreRelayerGovernance { // receiverValue (in this chain's currency), upper bound on gas, and the permissioned address allowed to execute this instruction DeliveryInstruction memory deliveryInstruction = container.instructions[targetParams.multisendIndex]; - // make sure that msg.sender is the permissioned address allowed to execute this instruction + // Check that msg.sender is the permissioned address allowed to execute this instruction if (fromWormholeFormat(deliveryInstruction.executionParameters.providerDeliveryAddress) != msg.sender) { revert IDelivery.UnexpectedRelayer(); } uint256 wormholeMessageFee = wormhole.messageFee(); - // make sure relayer passed in (as msg.value) sufficient funds + // Check that the relay provider passed in at least [(one wormhole message fee) + instruction.maximumRefund + instruction.receiverValue] of this chain's currency as msg.value if ( msg.value < deliveryInstruction.maximumRefundTarget + deliveryInstruction.receiverValueTarget + wormholeMessageFee @@ -313,7 +385,7 @@ contract CoreRelayerDelivery is CoreRelayerGovernance { revert IDelivery.InsufficientRelayerFunds(); } - // make sure 'targetChain' is the current chain + // Check that the instruction's target chain is this chain if (chainId() != deliveryInstruction.targetChain) { revert IDelivery.TargetChainIsNotThisChain(deliveryInstruction.targetChain); } diff --git a/ethereum/contracts/interfaces/IDelivery.sol b/ethereum/contracts/interfaces/IDelivery.sol index 0257bec..ed5cbe8 100644 --- a/ethereum/contracts/interfaces/IDelivery.sol +++ b/ethereum/contracts/interfaces/IDelivery.sol @@ -26,12 +26,20 @@ interface IDelivery { * @notice The relay provider calls 'deliverSingle' to relay messages as described by one delivery instruction * * The instruction specifies the target chain (must be this chain), target address, refund address, maximum refund (in this chain's currency), - * receiverValue (in this chain's currency), upper bound on gas, and the permissioned address allowed to execute this instruction + * receiver value (in this chain's currency), upper bound on gas, and the permissioned address allowed to execute this instruction * - * The relay provider must pass in the signed wormhole messages from the source chain of the same nonce + * The relay provider must pass in the signed wormhole messages (VAAs) from the source chain of the same nonce * (the wormhole message with the delivery instructions (the delivery VAA) must be one of these messages) * as well as identify which of these messages is the delivery VAA and which of the many instructions in the multichainSend container is meant to be executed * + * The messages will be relayed to the target address (with the specified gas limit and receiver value) iff the following checks are met: + * - the delivery VAA has a valid signature + * - the delivery VAA's emitter is one of these CoreRelayer contracts + * - the delivery instruction container in the delivery VAA was fully funded + * - the instruction's target chain is this chain + * - the relay provider passed in at least [(one wormhole message fee) + instruction.maximumRefundTarget + instruction.receiverValueTarget] of this chain's currency as msg.value + * - msg.sender is the permissioned address allowed to execute this instruction + * * @param targetParams struct containing the signed wormhole messages and encoded delivery instruction container (and other information) */ function deliverSingle(TargetDeliveryParametersSingle memory targetParams) external payable; @@ -60,6 +68,18 @@ interface IDelivery { * (the wormhole message with the original delivery instructions (the delivery VAA) must be one of these messages) * as well as the wormhole message with the new redelivery instruction (the redelivery VAA) * + * The messages will be relayed to the target address (with the specified gas limit and receiver value) iff the following checks are met: + * - the redelivery VAA (targetParams.redeliveryVM) has a valid signature + * - the redelivery VAA's emitter is one of these CoreRelayer contracts + * - the original delivery VAA has a valid signature + * - the original delivery VAA's emitter is one of these CoreRelayer contracts + * - the new redelivery instruction's upper bound on gas >= the original instruction's upper bound on gas + * - the new redelivery instruction's 'receiver value' amount >= the original instruction's 'receiver value' amount + * - the redelivery instruction's target chain = the original instruction's target chain = this chain + * - for the redelivery instruction, the relay provider passed in at least [(one wormhole message fee) + instruction.newMaximumRefundTarget + instruction.newReceiverValueTarget] of this chain's currency as msg.value + * - msg.sender is the permissioned address allowed to execute this redelivery instruction + * - msg.sender is the permissioned address allowed to execute the old instruction + * * @param targetParams struct containing the signed wormhole messages and encoded redelivery instruction (and other information) */ function redeliverSingle(TargetRedeliveryByTxHashParamsSingle memory targetParams) external payable; diff --git a/ethereum/contracts/interfaces/IWormholeRelayer.sol b/ethereum/contracts/interfaces/IWormholeRelayer.sol index aae7c4d..fca62ad 100644 --- a/ethereum/contracts/interfaces/IWormholeRelayer.sol +++ b/ethereum/contracts/interfaces/IWormholeRelayer.sol @@ -238,9 +238,9 @@ interface IWormholeRelayer { * @custom:member newMaxTransactionFee The new 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' - * This must be greater than or equal to the original maxTransactionFee paid in the original request + * This must pay for at least as much gas as was paid for in the original request * @custom:member receiverValue The amount (denominated in source chain currency) that will be converted to target chain currency and passed into the receiveWormholeMessage endpoint as value. - * This must be greater than or equal to the original receiverValue paid in the original request + * This must pay for at least as much receiver value as was paid for in the original request * @custom:member newRelayParameters This should be 'getDefaultRelayParameters()' */ struct ResendByTx {