diff --git a/ethereum/contracts/coreRelayer/CoreRelayer.sol b/ethereum/contracts/coreRelayer/CoreRelayer.sol index 1469783..4079cb4 100644 --- a/ethereum/contracts/coreRelayer/CoreRelayer.sol +++ b/ethereum/contracts/coreRelayer/CoreRelayer.sol @@ -3,15 +3,13 @@ pragma solidity ^0.8.0; -import "../libraries/external/BytesLib.sol"; +import "../interfaces/IWormholeRelayer.sol"; import "../interfaces/IWormholeReceiver.sol"; - +import "../interfaces/IDelivery.sol"; import "./CoreRelayerGovernance.sol"; import "./CoreRelayerStructs.sol"; contract CoreRelayer is CoreRelayerGovernance { - using BytesLib for bytes; - enum DeliveryStatus { SUCCESS, RECEIVER_FAILURE, @@ -28,113 +26,39 @@ contract CoreRelayer is CoreRelayerGovernance { DeliveryStatus status ); - error FundsTooMuch(); - error MaxTransactionFeeNotEnough(); - error MsgValueTooLow(); // msg.value must cover the budget specified - error NonceIsZero(); - error ForwardRequestFromWrongAddress(); - error NoDeliveryInProcess(); - error CantRequestMultipleForwards(); - error RelayProviderDoesNotSupportTargetChain(); - error RolloverChainNotIncluded(); // Rollover chain was not included in the forwarding request - error ChainNotFoundInSends(uint16 chainId); // Required chain not found in the delivery requests - error ReentrantCall(); - error InvalidEmitterInOriginalDeliveryVM(uint8 index); - error InvalidRedeliveryVM(string reason); - error InvalidEmitterInRedeliveryVM(); - error MismatchingRelayProvidersInRedelivery(); // The same relay provider must be specified when doing a single VAA redeliver - error InvalidVaa(uint8 index); - error InvalidEmitter(); - error SendNotSufficientlyFunded(); // This delivery request was not sufficiently funded, and must request redelivery - error UnexpectedRelayer(); // Specified relayer is not the relayer delivering the message - error InsufficientRelayerFunds(); // The relayer didn't pass sufficient funds (msg.value does not cover the necessary budget fees) - error AlreadyDelivered(); // The message was already delivered. - error TargetChainIsNotThisChain(uint16 targetChainId); - - function send(Send memory request, uint32 nonce, IRelayProvider provider) + function send(IWormholeRelayer.Send memory request, uint32 nonce, address relayProvider) public payable returns (uint64 sequence) { - Send[] memory requests = new Send[](1); - requests[0] = request; - MultichainSend memory container = MultichainSend({relayProviderAddress: address(provider), requests: requests}); - return multichainSend(container, nonce); + return multichainSend(multichainSendContainer(request, relayProvider), nonce); } - function forward(Send memory request, uint32 nonce, IRelayProvider provider) public payable { - Send[] memory requests = new Send[](1); - requests[0] = request; - MultichainSend memory container = MultichainSend({relayProviderAddress: address(provider), requests: requests}); - return multichainForward(container, request.targetChain, nonce); + function forward(IWormholeRelayer.Send memory request, uint32 nonce, address relayProvider) public payable { + return multichainForward(multichainSendContainer(request, relayProvider), nonce); } - function resend(ResendByTx memory request, uint32 nonce, IRelayProvider provider) + function resend(IWormholeRelayer.ResendByTx memory request, uint32 nonce, address relayProvider) public payable returns (uint64 sequence) { - (uint256 requestFee, uint256 maximumRefund, uint256 receiverValueTarget, bool isSufficient, uint8 reason) = - verifyFunding( - VerifyFundingCalculation({ - provider: provider, - sourceChain: chainId(), - targetChain: request.targetChain, - maxTransactionFeeSource: request.newMaxTransactionFee, - receiverValueSource: request.newReceiverValue, - isDelivery: false - }) - ); + updateWormholeMessageFee(); + bool isSufficient = request.newMaxTransactionFee + request.newReceiverValue + wormholeMessageFee() <= msg.value; if (!isSufficient) { - if (reason == 26) { - revert MaxTransactionFeeNotEnough(); - } else { - revert FundsTooMuch(); - } - } - IWormhole wormhole = wormhole(); - uint256 wormholeMessageFee = wormhole.messageFee(); - uint256 totalFee = requestFee + wormholeMessageFee; - - //Make sure the msg.value covers the budget they specified - if (msg.value < totalFee) { - revert MsgValueTooLow(); + revert IWormholeRelayer.MsgValueTooLow(); } - sequence = emitRedelivery( - request, - nonce, - provider.getConsistencyLevel(), - receiverValueTarget, - maximumRefund, - provider, - wormhole, - wormholeMessageFee + IRelayProvider provider = IRelayProvider(relayProvider); + RedeliveryByTxHashInstruction memory instruction = convertResendToRedeliveryInstruction(request, provider); + checkRedeliveryInstruction(instruction, provider); + + sequence = wormhole().publishMessage{value: wormholeMessageFee()}( + nonce, encodeRedeliveryInstruction(instruction), provider.getConsistencyLevel() ); //Send the delivery fees to the specified address of the provider. - pay(provider.getRewardAddress(), msg.value - wormholeMessageFee); - } - - function emitRedelivery( - ResendByTx memory request, - uint32 nonce, - uint8 consistencyLevel, - uint256 receiverValueTarget, - uint256 maximumRefund, - IRelayProvider provider, - IWormhole wormhole, - uint256 wormholeMessageFee - ) internal returns (uint64 sequence) { - bytes memory instruction = convertToEncodedRedeliveryByTxHashInstruction( - request, - receiverValueTarget, - maximumRefund, - calculateTargetGasRedeliveryAmount(request.targetChain, request.newMaxTransactionFee, provider), - provider - ); - - sequence = wormhole.publishMessage{value: wormholeMessageFee}(nonce, instruction, consistencyLevel); + pay(provider.getRewardAddress(), msg.value - wormholeMessageFee()); } /** @@ -147,37 +71,33 @@ contract CoreRelayer is CoreRelayerGovernance { * 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 */ - function multichainSend(MultichainSend memory deliveryRequests, uint32 nonce) + function multichainSend(IWormholeRelayer.MultichainSend memory deliveryRequests, uint32 nonce) public payable returns (uint64 sequence) { - (uint256 totalCost, bool isSufficient, uint8 cause) = sufficientFundsHelper(deliveryRequests, msg.value); - if (!isSufficient) { - if (cause == 26) { - revert MaxTransactionFeeNotEnough(); - } else if (cause == 25) { - revert MsgValueTooLow(); - } else { - revert FundsTooMuch(); - } + updateWormholeMessageFee(); + uint256 totalFee = getTotalFeeMultichainSend(deliveryRequests); + if (totalFee > msg.value) { + revert IWormholeRelayer.MsgValueTooLow(); } if (nonce == 0) { - revert NonceIsZero(); + revert IWormholeRelayer.NonceIsZero(); } - // encode the DeliveryInstructions - bytes memory container = convertToEncodedDeliveryInstructions(deliveryRequests, true); + IRelayProvider relayProvider = IRelayProvider(deliveryRequests.relayProviderAddress); + DeliveryInstructionsContainer memory container = + convertMultichainSendToDeliveryInstructionsContainer(deliveryRequests); + checkInstructions(container, IRelayProvider(deliveryRequests.relayProviderAddress)); + container.sufficientlyFunded = true; // emit delivery message - IWormhole wormhole = wormhole(); - IRelayProvider provider = IRelayProvider(deliveryRequests.relayProviderAddress); - uint256 wormholeMessageFee = wormhole.messageFee(); - - sequence = wormhole.publishMessage{value: wormholeMessageFee}(nonce, container, provider.getConsistencyLevel()); + sequence = wormhole().publishMessage{value: wormholeMessageFee()}( + nonce, encodeDeliveryInstructionsContainer(container), relayProvider.getConsistencyLevel() + ); //pay fee to provider - pay(provider.getRewardAddress(), totalCost - wormholeMessageFee); + pay(relayProvider.getRewardAddress(), totalFee - wormholeMessageFee()); } /** @@ -191,218 +111,96 @@ contract CoreRelayer is CoreRelayerGovernance { * 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 */ - function multichainForward(MultichainSend memory deliveryRequests, uint16 rolloverChain, uint32 nonce) - public - payable - { - // Can only forward while a delivery is in process. + function multichainForward(IWormholeRelayer.MultichainSend memory deliveryRequests, uint32 nonce) public payable { if (!isContractLocked()) { - revert NoDeliveryInProcess(); + revert IWormholeRelayer.NoDeliveryInProgress(); } - if (getForwardingRequest().isValid) { - revert CantRequestMultipleForwards(); + if (getForwardInstruction().isValid) { + revert IWormholeRelayer.MultipleForwardsRequested(); + } + if (nonce == 0) { + revert IWormholeRelayer.NonceIsZero(); + } + if (msg.sender != lockedTargetAddress()) { + revert IWormholeRelayer.ForwardRequestFromWrongAddress(); } - //We want to catch malformed requests in this function, and only underfunded requests when emitting. - verifyForwardingRequest(deliveryRequests, rolloverChain, nonce); + uint256 totalFee = getTotalFeeMultichainSend(deliveryRequests); + DeliveryInstructionsContainer memory container = + convertMultichainSendToDeliveryInstructionsContainer(deliveryRequests); + checkInstructions(container, IRelayProvider(deliveryRequests.relayProviderAddress)); - bytes memory encodedMultichainSend = encodeMultichainSend(deliveryRequests); - setForwardingRequest( - ForwardingRequest({ - deliveryRequestsContainer: encodedMultichainSend, - rolloverChain: rolloverChain, + setForwardInstruction( + ForwardInstruction({ + container: container, nonce: nonce, msgValue: msg.value, + totalFee: totalFee, sender: msg.sender, + relayProvider: deliveryRequests.relayProviderAddress, isValid: true }) ); } - function emitForward(uint256 refundAmount, ForwardingRequest memory forwardingRequest) + function emitForward(uint256 transactionFeeRefundAmount, ForwardInstruction memory forwardInstruction) internal - returns (uint64, bool) + returns (bool forwardIsFunded) { - MultichainSend memory container = decodeMultichainSend(forwardingRequest.deliveryRequestsContainer); + DeliveryInstructionsContainer memory container = forwardInstruction.container; //Add any additional funds which were passed in to the refund amount - refundAmount = refundAmount + forwardingRequest.msgValue; + transactionFeeRefundAmount = transactionFeeRefundAmount + forwardInstruction.msgValue; //make sure the refund amount covers the native gas amounts - (uint256 totalMinimumFees, bool funded,) = sufficientFundsHelper(container, refundAmount); + forwardIsFunded = (transactionFeeRefundAmount >= forwardInstruction.totalFee); + container.sufficientlyFunded = forwardIsFunded; - //REVISE consider deducting the cost of this process from the refund amount? + IRelayProvider relayProvider = IRelayProvider(forwardInstruction.relayProvider); - if (funded) { - //find the delivery instruction for the rollover chain - uint16 rolloverInstructionIndex = findDeliveryIndex(container, forwardingRequest.rolloverChain); - - //calc how much budget is used by chains other than the rollover chain - uint256 rolloverChainCostEstimate = container.requests[rolloverInstructionIndex].maxTransactionFee - + container.requests[rolloverInstructionIndex].receiverValue; - //uint256 nonrolloverBudget = totalMinimumFees - rolloverChainCostEstimate; //stack too deep - uint256 rolloverBudget = refundAmount - (totalMinimumFees - rolloverChainCostEstimate) - - container.requests[rolloverInstructionIndex].receiverValue; - - //overwrite the gas budget on the rollover chain to the remaining budget amount - container.requests[rolloverInstructionIndex].maxTransactionFee = rolloverBudget; + 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 - bytes memory reencoded = convertToEncodedDeliveryInstructions(container, funded); - IRelayProvider provider = IRelayProvider(container.relayProviderAddress); - IWormhole wormhole = wormhole(); - uint64 sequence = wormhole.publishMessage{value: wormhole.messageFee()}( - forwardingRequest.nonce, reencoded, provider.getConsistencyLevel() + 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 (funded) { - pay(provider.getRewardAddress(), refundAmount); + if (forwardIsFunded) { + pay(relayProvider.getRewardAddress(), transactionFeeRefundAmount); } //clear forwarding request from cache - clearForwardingRequest(); - - return (sequence, funded); + clearForwardInstruction(); } - function verifyForwardingRequest(MultichainSend memory container, uint16 rolloverChain, uint32 nonce) - internal - view - { - if (nonce == 0) { - revert NonceIsZero(); - } - - if (msg.sender != lockedTargetAddress()) { - revert ForwardRequestFromWrongAddress(); - } - - bool foundRolloverChain = false; - IRelayProvider selectedProvider = IRelayProvider(container.relayProviderAddress); - - for (uint16 i = 0; i < container.requests.length; i++) { - // TODO: Optimization opportunity here by reducing multiple calls to only one with all requested addresses. - if (selectedProvider.getDeliveryAddress(container.requests[i].targetChain) == 0) { - revert RelayProviderDoesNotSupportTargetChain(); - } - if (container.requests[i].targetChain == rolloverChain) { - foundRolloverChain = true; - } - } - - if (!foundRolloverChain) { - revert RolloverChainNotIncluded(); - } - } - - function findDeliveryIndex(MultichainSend memory container, uint16 chainId) + function multichainSendContainer(IWormholeRelayer.Send memory request, address relayProvider) internal pure - returns (uint16 deliveryRequestIndex) + returns (IWormholeRelayer.MultichainSend memory container) { - for (uint16 i = 0; i < container.requests.length; i++) { - if (container.requests[i].targetChain == chainId) { - deliveryRequestIndex = i; - return deliveryRequestIndex; - } - } - - revert ChainNotFoundInSends(chainId); - } - - /* - By the time this function completes, we must be certain that the specified funds are sufficient to cover - delivery for each one of the deliveryRequests with at least 1 gas on the target chains. - */ - function sufficientFundsHelper(MultichainSend memory deliveryRequests, uint256 funds) - internal - view - returns (uint256 totalFees, bool isSufficient, uint8 reason) - { - totalFees = wormhole().messageFee(); - IRelayProvider provider = IRelayProvider(deliveryRequests.relayProviderAddress); - - for (uint256 i = 0; i < deliveryRequests.requests.length; i++) { - Send memory request = deliveryRequests.requests[i]; - - (uint256 requestFee, uint256 maximumRefund, uint256 receiverValueTarget, bool isSufficient, uint8 reason) = - verifyFunding( - VerifyFundingCalculation({ - provider: provider, - sourceChain: chainId(), - targetChain: request.targetChain, - maxTransactionFeeSource: request.maxTransactionFee, - receiverValueSource: request.receiverValue, - isDelivery: true - }) - ); - - if (!isSufficient) { - return (0, false, reason); - } - - totalFees = totalFees + requestFee; - if (funds < totalFees) { - return (0, false, 25); //"Insufficient funds were provided to cover the delivery fees."); - } - } - - return (totalFees, true, 0); - } - - struct VerifyFundingCalculation { - IRelayProvider provider; - uint16 sourceChain; - uint16 targetChain; - uint256 maxTransactionFeeSource; - uint256 receiverValueSource; - bool isDelivery; - } - - function verifyFunding(VerifyFundingCalculation memory args) - internal - view - returns ( - uint256 requestFee, - uint256 maximumRefund, - uint256 receiverValueTarget, - bool isSufficient, - uint8 reason - ) - { - requestFee = args.maxTransactionFeeSource + args.receiverValueSource; - receiverValueTarget = convertApplicationBudgetAmount(args.receiverValueSource, args.targetChain, args.provider); - uint256 overheadFeeSource = args.isDelivery - ? args.provider.quoteDeliveryOverhead(args.targetChain) - : args.provider.quoteRedeliveryOverhead(args.targetChain); - uint256 overheadBudgetTarget = - assetConversionHelper(args.sourceChain, overheadFeeSource, args.targetChain, 1, 1, true, args.provider); - maximumRefund = args.isDelivery - ? calculateTargetDeliveryMaximumRefund(args.targetChain, args.maxTransactionFeeSource, args.provider) - : calculateTargetRedeliveryMaximumRefund(args.targetChain, args.maxTransactionFeeSource, args.provider); - - //Make sure the maxTransactionFee covers the minimum delivery cost to the targetChain - if (args.maxTransactionFeeSource < overheadFeeSource) { - isSufficient = false; - reason = 26; //Insufficient msg.value to cover minimum delivery costs."; - } - //Make sure the budget does not exceed the maximum for the provider on that chain; //This added value is totalBudgetTarget - else if ( - args.provider.quoteMaximumBudget(args.targetChain) - < (maximumRefund + overheadBudgetTarget + receiverValueTarget) - ) { - isSufficient = false; - reason = 27; //"Specified budget exceeds the maximum allowed by the provider"; - } else { - isSufficient = true; - reason = 0; - } + IWormholeRelayer.Send[] memory requests = new IWormholeRelayer.Send[](1); + requests[0] = request; + container = IWormholeRelayer.MultichainSend({relayProviderAddress: relayProvider, requests: requests}); } function _executeDelivery( - IWormhole wormhole, DeliveryInstruction memory internalInstruction, bytes[] memory encodedVMs, bytes32 deliveryVaaHash, @@ -410,11 +208,11 @@ contract CoreRelayer is CoreRelayerGovernance { uint16 sourceChain, uint64 sourceSequence ) internal { - //REVISE Decide whether we want to remove the DeliveryInstructionContainer from encodedVMs. + //REVISE Decide whether we want to remove the DeliveryInstructionsContainer from encodedVMs. // lock the contract to prevent reentrancy if (isContractLocked()) { - revert ReentrantCall(); + revert IDelivery.ReentrantCall(); } setContractLock(true); setLockedTargetAddress(fromWormholeFormat(internalInstruction.targetAddress)); @@ -422,7 +220,7 @@ contract CoreRelayer is CoreRelayerGovernance { uint256 preGas = gasleft(); // call the receiveWormholeMessages endpoint on the target contract - (bool success,) = fromWormholeFormat(internalInstruction.targetAddress).call{ + (bool callToTargetContractSucceeded,) = fromWormholeFormat(internalInstruction.targetAddress).call{ gas: internalInstruction.executionParameters.gasLimit, value: internalInstruction.receiverValueTarget }(abi.encodeCall(IWormholeReceiver.receiveWormholeMessages, (encodedVMs, new bytes[](0)))); @@ -438,126 +236,49 @@ contract CoreRelayer is CoreRelayerGovernance { : (preGas - postGas); // refund unused gas budget - uint256 weiToRefund = internalInstruction.receiverValueTarget; - if (success) { - weiToRefund = (internalInstruction.executionParameters.gasLimit - gasUsed) - * internalInstruction.maximumRefundTarget / internalInstruction.executionParameters.gasLimit; - } + uint256 transactionFeeRefundAmount = (internalInstruction.executionParameters.gasLimit - gasUsed) + * internalInstruction.maximumRefundTarget / internalInstruction.executionParameters.gasLimit; // unlock the contract setContractLock(false); - //REVISE decide if we want to always emit a VAA, or only emit a msg when forwarding - // // emit delivery status message - // DeliveryStatus memory status = DeliveryStatus({ - // payloadID: 2, - // batchHash: internalParams.batchVM.hash, - // emitterAddress: internalParams.deliveryId.emitterAddress, - // sequence: internalParams.deliveryId.sequence, - // deliveryCount: uint16(stackTooDeep.attemptedDeliveryCount + 1), - // deliverySuccess: success - // }); - // // set the nonce to zero so a batch VAA is not created - // sequence = - // wormhole.publishMessage{value: wormhole.messageFee()}(0, encodeDeliveryStatus(status), consistencyLevel()); - ForwardingRequest memory forwardingRequest = getForwardingRequest(); + ForwardInstruction memory forwardingRequest = getForwardInstruction(); + DeliveryStatus status; + bool forwardIsFunded = false; if (forwardingRequest.isValid) { - (, success) = emitForward(weiToRefund, forwardingRequest); - if (success) { - emit Delivery({ - recipientContract: fromWormholeFormat(internalInstruction.targetAddress), - sourceChain: sourceChain, - sequence: sourceSequence, - deliveryVaaHash: deliveryVaaHash, - status: DeliveryStatus.FORWARD_REQUEST_SUCCESS - }); - } else { - bool sent = pay(payable(fromWormholeFormat(internalInstruction.refundAddress)), weiToRefund); - if (!sent) { - // if refunding fails, pay out full refund to relayer - weiToRefund = 0; - } - emit Delivery({ - recipientContract: fromWormholeFormat(internalInstruction.targetAddress), - sourceChain: sourceChain, - sequence: sourceSequence, - deliveryVaaHash: deliveryVaaHash, - status: DeliveryStatus.FORWARD_REQUEST_FAILURE - }); - } + forwardIsFunded = emitForward(transactionFeeRefundAmount, forwardingRequest); + status = forwardIsFunded ? DeliveryStatus.FORWARD_REQUEST_SUCCESS : DeliveryStatus.FORWARD_REQUEST_FAILURE; } else { - bool sent = pay(payable(fromWormholeFormat(internalInstruction.refundAddress)), weiToRefund); - if (!sent) { - // if refunding fails, pay out full refund to relayer - weiToRefund = 0; - } - - if (success) { - emit Delivery({ - recipientContract: fromWormholeFormat(internalInstruction.targetAddress), - sourceChain: sourceChain, - sequence: sourceSequence, - deliveryVaaHash: deliveryVaaHash, - status: DeliveryStatus.SUCCESS - }); - } else { - emit Delivery({ - recipientContract: fromWormholeFormat(internalInstruction.targetAddress), - sourceChain: sourceChain, - sequence: sourceSequence, - deliveryVaaHash: deliveryVaaHash, - status: DeliveryStatus.RECEIVER_FAILURE - }); - } + status = callToTargetContractSucceeded ? DeliveryStatus.SUCCESS : DeliveryStatus.RECEIVER_FAILURE; } - uint256 receiverValuePaid = (success ? internalInstruction.receiverValueTarget : 0); - uint256 wormholeFeePaid = forwardingRequest.isValid ? wormhole.messageFee() : 0; - uint256 relayerRefundAmount = msg.value - weiToRefund - receiverValuePaid - wormholeFeePaid; + 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); } - //REVISE, consider implementing this system into the RelayProvider. - // function requestRewardPayout(uint16 rewardChain, bytes32 receiver, uint32 nonce) - // public - // payable - // returns (uint64 sequence) - // { - // uint256 amount = relayerRewards(msg.sender, rewardChain); - - // require(amount > 0, "no current accrued rewards"); - - // resetRelayerRewards(msg.sender, rewardChain); - - // sequence = wormhole().publishMessage{value: msg.value}( - // nonce, - // encodeRewardPayout( - // RewardPayout({ - // payloadID: 100, - // fromChain: chainId(), - // chain: rewardChain, - // amount: amount, - // receiver: receiver - // }) - // ), - // 20 //REVISE encode finality - // ); - // } - - // function collectRewards(bytes memory encodedVm) public { - // (IWormhole.VM memory vm, bool valid, string memory reason) = wormhole().parseAndVerifyVM(encodedVm); - - // require(valid, reason); - // require(verifyRelayerVM(vm), "invalid emitter"); - - // RewardPayout memory payout = parseRewardPayout(vm.payload); - - // require(payout.chain == chainId()); - - // payable(address(uint160(uint256(payout.receiver)))).transfer(payout.amount); - // } - function verifyRelayerVM(IWormhole.VM memory vm) internal view returns (bool) { return registeredCoreRelayerContract(vm.emitterChainId) == vm.emitterAddress; } @@ -566,34 +287,34 @@ contract CoreRelayer is CoreRelayerGovernance { return defaultRelayProvider(); } - function redeliverSingle(TargetRedeliveryByTxHashParamsSingle memory targetParams) public payable { + 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 InvalidRedeliveryVM(reason); + revert IDelivery.InvalidRedeliveryVM(reason); } if (!verifyRelayerVM(redeliveryVM)) { // Redelivery VM has an invalid emitter - revert InvalidEmitterInRedeliveryVM(); + revert IDelivery.InvalidEmitterInRedeliveryVM(); } - RedeliveryByTxHashInstruction memory redeliveryInstruction = - decodeRedeliveryByTxHashInstruction(redeliveryVM.payload); + 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 InvalidVaa(redeliveryInstruction.deliveryIndex); + revert IDelivery.InvalidVaa(redeliveryInstruction.deliveryIndex, reason); } if (!verifyRelayerVM(originalDeliveryVM)) { // Original Delivery VM has a invalid emitter - revert InvalidEmitterInOriginalDeliveryVM(redeliveryInstruction.deliveryIndex); + revert IDelivery.InvalidEmitterInOriginalDeliveryVM(redeliveryInstruction.deliveryIndex); } DeliveryInstruction memory instruction; @@ -616,7 +337,6 @@ contract CoreRelayer is CoreRelayerGovernance { } _executeDelivery( - wormhole, instruction, targetParams.sourceEncodedVMs, originalDeliveryVM.hash, @@ -635,16 +355,16 @@ contract CoreRelayer is CoreRelayerGovernance { // 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 MismatchingRelayProvidersInRedelivery(); + revert IDelivery.MismatchingRelayProvidersInRedelivery(); } // relayer must have covered the necessary funds if ( msg.value < redeliveryInstruction.newMaximumRefundTarget + redeliveryInstruction.newReceiverValueTarget - + wormhole().messageFee() + + wormholeMessageFee() ) { - revert InsufficientRelayerFunds(); + revert IDelivery.InsufficientRelayerFunds(); } uint16 whChainId = chainId(); @@ -670,24 +390,25 @@ contract CoreRelayer is CoreRelayerGovernance { deliveryInstruction.executionParameters = redeliveryInstruction.executionParameters; } - function deliverSingle(TargetDeliveryParametersSingle memory targetParams) public payable { + 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 InvalidVaa(targetParams.deliveryIndex); + revert IDelivery.InvalidVaa(targetParams.deliveryIndex, reason); } if (!verifyRelayerVM(deliveryVM)) { - revert InvalidEmitter(); + revert IDelivery.InvalidEmitter(); } DeliveryInstructionsContainer memory container = decodeDeliveryInstructionsContainer(deliveryVM.payload); //ensure this is a funded delivery, not a failed forward. if (!container.sufficientlyFunded) { - revert SendNotSufficientlyFunded(); + revert IDelivery.SendNotSufficientlyFunded(); } // parse the deliveryVM payload into the DeliveryInstructions struct @@ -695,24 +416,23 @@ contract CoreRelayer is CoreRelayerGovernance { //make sure the specified relayer is the relayer delivering this message if (fromWormholeFormat(deliveryInstruction.executionParameters.providerDeliveryAddress) != msg.sender) { - revert UnexpectedRelayer(); + revert IDelivery.UnexpectedRelayer(); } //make sure relayer passed in sufficient funds if ( msg.value - < deliveryInstruction.maximumRefundTarget + deliveryInstruction.receiverValueTarget + wormhole.messageFee() + < deliveryInstruction.maximumRefundTarget + deliveryInstruction.receiverValueTarget + wormholeMessageFee() ) { - revert InsufficientRelayerFunds(); + revert IDelivery.InsufficientRelayerFunds(); } //make sure this delivery is intended for this chain if (chainId() != deliveryInstruction.targetChain) { - revert TargetChainIsNotThisChain(deliveryInstruction.targetChain); + revert IDelivery.TargetChainIsNotThisChain(deliveryInstruction.targetChain); } _executeDelivery( - wormhole, deliveryInstruction, targetParams.encodedVMs, deliveryVM.hash, @@ -734,107 +454,6 @@ contract CoreRelayer is CoreRelayerGovernance { return new bytes(0); } - function makeRelayerParams(IRelayProvider provider) public pure returns (bytes memory relayerParams) { - //current version is just 1, - relayerParams = abi.encode(1, toWormholeFormat(address(provider))); - } - - function getDeliveryInstructionsContainer(bytes memory encoded) - public - view - returns (DeliveryInstructionsContainer memory container) - { - container = decodeDeliveryInstructionsContainer(encoded); - } - - function getRedeliveryByTxHashInstruction(bytes memory encoded) - public - view - returns (RedeliveryByTxHashInstruction memory instruction) - { - instruction = decodeRedeliveryByTxHashInstruction(encoded); - } - - /** - * Given a targetChain, maxTransactionFee, and a relay provider, this function calculates what the gas limit of the delivery transaction - * should be. - */ - function calculateTargetGasDeliveryAmount(uint16 targetChain, uint256 maxTransactionFee, IRelayProvider provider) - internal - view - returns (uint32 gasAmount) - { - gasAmount = calculateTargetGasDeliveryAmountHelper( - targetChain, maxTransactionFee, provider.quoteDeliveryOverhead(targetChain), provider - ); - } - - function calculateTargetDeliveryMaximumRefund( - uint16 targetChain, - uint256 maxTransactionFee, - IRelayProvider provider - ) internal view returns (uint256 maximumRefund) { - maximumRefund = calculateTargetDeliveryMaximumRefundHelper( - targetChain, maxTransactionFee, provider.quoteDeliveryOverhead(targetChain), provider - ); - } - - /** - * Given a targetChain, maxTransactionFee, and a relay provider, this function calculates what the gas limit of the redelivery transaction - * should be. - */ - function calculateTargetGasRedeliveryAmount(uint16 targetChain, uint256 maxTransactionFee, IRelayProvider provider) - internal - view - returns (uint32 gasAmount) - { - gasAmount = calculateTargetGasDeliveryAmountHelper( - targetChain, maxTransactionFee, provider.quoteRedeliveryOverhead(targetChain), provider - ); - } - - function calculateTargetRedeliveryMaximumRefund( - uint16 targetChain, - uint256 maxTransactionFee, - IRelayProvider provider - ) internal view returns (uint256 maximumRefund) { - maximumRefund = calculateTargetDeliveryMaximumRefundHelper( - targetChain, maxTransactionFee, provider.quoteRedeliveryOverhead(targetChain), provider - ); - } - - function calculateTargetGasDeliveryAmountHelper( - uint16 targetChain, - uint256 maxTransactionFee, - uint256 deliveryOverhead, - IRelayProvider provider - ) internal view returns (uint32 gasAmount) { - if (maxTransactionFee <= deliveryOverhead) { - gasAmount = 0; - } else { - uint256 gas = (maxTransactionFee - deliveryOverhead) / provider.quoteGasPrice(targetChain); - if (gas > type(uint32).max) { - gasAmount = type(uint32).max; - } else { - gasAmount = uint32(gas); - } - } - } - - function calculateTargetDeliveryMaximumRefundHelper( - uint16 targetChain, - uint256 maxTransactionFee, - uint256 deliveryOverhead, - IRelayProvider provider - ) internal view returns (uint256 maximumRefund) { - if (maxTransactionFee >= deliveryOverhead) { - uint256 remainder = maxTransactionFee - deliveryOverhead; - maximumRefund = assetConversionHelper(chainId(), remainder, targetChain, 1, 1, false, provider); - } else { - maximumRefund = 0; - } - } - function quoteGas(uint16 targetChain, uint32 gasLimit, IRelayProvider provider) public view @@ -852,33 +471,6 @@ contract CoreRelayer is CoreRelayerGovernance { provider.quoteRedeliveryOverhead(targetChain) + (gasLimit * provider.quoteGasPrice(targetChain)); } - function assetConversionHelper( - uint16 sourceChain, - uint256 sourceAmount, - uint16 targetChain, - uint256 multiplier, - uint256 multiplierDenominator, - bool roundUp, - IRelayProvider provider - ) internal view returns (uint256 targetAmount) { - uint256 srcNativeCurrencyPrice = provider.quoteAssetPrice(sourceChain); - if (srcNativeCurrencyPrice == 0) { - revert RelayProviderDoesNotSupportTargetChain(); - } - - uint256 dstNativeCurrencyPrice = provider.quoteAssetPrice(targetChain); - if (dstNativeCurrencyPrice == 0) { - revert RelayProviderDoesNotSupportTargetChain(); - } - uint256 numerator = sourceAmount * srcNativeCurrencyPrice * multiplier; - uint256 denominator = dstNativeCurrencyPrice * multiplierDenominator; - if (roundUp) { - targetAmount = (numerator + denominator - 1) / denominator; - } else { - targetAmount = numerator / denominator; - } - } - //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 @@ -891,81 +483,6 @@ contract CoreRelayer is CoreRelayerGovernance { ); } - //This should invert quoteApplicationBudgetAmount, I.E when a user pays the sourceAmount, they receive at least the value of targetAmount they requested from - //quoteReceiverValue. - function convertApplicationBudgetAmount(uint256 sourceAmount, uint16 targetChain, IRelayProvider provider) - internal - view - returns (uint256 targetAmount) - { - (uint16 buffer, uint16 denominator) = provider.getAssetConversionBuffer(targetChain); - - targetAmount = assetConversionHelper( - chainId(), sourceAmount, targetChain, denominator, uint256(0) + denominator + buffer, false, provider - ); - } - - function convertToEncodedRedeliveryByTxHashInstruction( - ResendByTx memory request, - uint256 receiverValueTarget, - uint256 maximumRefund, - uint32 gasLimit, - IRelayProvider provider - ) internal view returns (bytes memory encoded) { - encoded = abi.encodePacked( - uint8(2), //version payload number - uint16(request.sourceChain), - bytes32(request.sourceTxHash), - uint32(request.sourceNonce), - uint16(request.targetChain), - uint8(request.deliveryIndex), - uint8(request.multisendIndex), - maximumRefund, - receiverValueTarget, - uint8(1), //version for ExecutionParameters - gasLimit, - provider.getDeliveryAddress(request.targetChain) - ); - } - - function convertToEncodedDeliveryInstructions(MultichainSend memory container, bool isFunded) - internal - view - returns (bytes memory encoded) - { - encoded = abi.encodePacked( - uint8(1), //version payload number - uint8(isFunded ? 1 : 0), // sufficiently funded - uint8(container.requests.length) //number of requests in the array - ); - - // TODO: this probably results in a quadratic algorithm. Further optimization can be done here. - // Append all the messages to the array. - for (uint256 i = 0; i < container.requests.length; i++) { - encoded = appendDeliveryInstruction( - encoded, container.requests[i], IRelayProvider(container.relayProviderAddress) - ); - } - } - - function appendDeliveryInstruction(bytes memory encoded, Send memory request, IRelayProvider provider) - internal - view - returns (bytes memory newEncoded) - { - newEncoded = abi.encodePacked( - encoded, - request.targetChain, - request.targetAddress, - request.refundAddress, - calculateTargetDeliveryMaximumRefund(request.targetChain, request.maxTransactionFee, provider), - convertApplicationBudgetAmount(request.receiverValue, request.targetChain, provider), - uint8(1), //version for ExecutionParameters - calculateTargetGasDeliveryAmount(request.targetChain, request.maxTransactionFee, provider), - provider.getDeliveryAddress(request.targetChain) - ); - } - function pay(address payable receiver, uint256 amount) internal returns (bool success) { if (amount > 0) { (success,) = receiver.call{value: amount}(""); diff --git a/ethereum/contracts/coreRelayer/CoreRelayerGetters.sol b/ethereum/contracts/coreRelayer/CoreRelayerGetters.sol index e32d956..e1e3189 100644 --- a/ethereum/contracts/coreRelayer/CoreRelayerGetters.sol +++ b/ethereum/contracts/coreRelayer/CoreRelayerGetters.sol @@ -33,6 +33,10 @@ 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; } @@ -53,8 +57,8 @@ contract CoreRelayerGetters is CoreRelayerState { return IRelayProvider(_state.defaultRelayProvider); } - function getForwardingRequest() internal view returns (CoreRelayerStructs.ForwardingRequest memory) { - return _state.forwardingRequest; + function getForwardInstruction() internal view returns (CoreRelayerStructs.ForwardInstruction memory) { + return _state.forwardInstruction; } function isContractLocked() internal view returns (bool) { diff --git a/ethereum/contracts/coreRelayer/CoreRelayerMessages.sol b/ethereum/contracts/coreRelayer/CoreRelayerMessages.sol index f889cf8..1149965 100644 --- a/ethereum/contracts/coreRelayer/CoreRelayerMessages.sol +++ b/ethereum/contracts/coreRelayer/CoreRelayerMessages.sol @@ -7,22 +7,302 @@ import "../libraries/external/BytesLib.sol"; import "./CoreRelayerGetters.sol"; import "./CoreRelayerStructs.sol"; +import "../interfaces/IWormholeRelayer.sol"; contract CoreRelayerMessages is CoreRelayerStructs, CoreRelayerGetters { using BytesLib for bytes; error InvalidPayloadId(uint8 payloadId); error InvalidDeliveryInstructionsPayload(uint256 length); - error InvalidSendsPayload(uint256 length); - function decodeRedeliveryByTxHashInstruction(bytes memory encoded) + function getTotalFeeMultichainSend(IWormholeRelayer.MultichainSend memory sendContainer) internal + view + returns (uint256 totalFee) + { + totalFee = wormholeMessageFee(); + uint256 length = sendContainer.requests.length; + for (uint256 i = 0; i < length; i++) { + IWormholeRelayer.Send memory request = sendContainer.requests[i]; + totalFee += request.maxTransactionFee + request.receiverValue; + } + } + + function convertMultichainSendToDeliveryInstructionsContainer(IWormholeRelayer.MultichainSend memory sendContainer) + internal + view + returns (DeliveryInstructionsContainer memory instructionsContainer) + { + instructionsContainer.payloadId = 1; + IRelayProvider relayProvider = IRelayProvider(sendContainer.relayProviderAddress); + uint256 length = sendContainer.requests.length; + instructionsContainer.instructions = new DeliveryInstruction[](length); + for (uint256 i = 0; i < length; i++) { + instructionsContainer.instructions[i] = + convertSendToDeliveryInstruction(sendContainer.requests[i], relayProvider); + } + } + + function convertSendToDeliveryInstruction(IWormholeRelayer.Send memory send, IRelayProvider relayProvider) + internal + view + returns (DeliveryInstruction memory instruction) + { + instruction.targetChain = send.targetChain; + instruction.targetAddress = send.targetAddress; + instruction.refundAddress = send.refundAddress; + instruction.maximumRefundTarget = + calculateTargetDeliveryMaximumRefund(send.targetChain, send.maxTransactionFee, relayProvider); + instruction.receiverValueTarget = + convertReceiverValueAmount(send.receiverValue, send.targetChain, relayProvider); + instruction.executionParameters = ExecutionParameters({ + version: 1, + gasLimit: calculateTargetGasDeliveryAmount(send.targetChain, send.maxTransactionFee, relayProvider), + providerDeliveryAddress: relayProvider.getDeliveryAddress(send.targetChain) + }); + } + + function checkInstructions(DeliveryInstructionsContainer memory container, IRelayProvider relayProvider) + internal + view + { + uint256 length = container.instructions.length; + for (uint8 i = 0; i < length; i++) { + DeliveryInstruction memory instruction = container.instructions[i]; + if (instruction.executionParameters.gasLimit == 0) { + revert IWormholeRelayer.MaxTransactionFeeNotEnough(i); + } + if ( + instruction.maximumRefundTarget + instruction.receiverValueTarget + wormholeMessageFee() + > relayProvider.quoteMaximumBudget(instruction.targetChain) + ) { + revert IWormholeRelayer.FundsTooMuch(i); + } + } + } + + function checkRedeliveryInstruction(RedeliveryByTxHashInstruction memory instruction, IRelayProvider relayProvider) + internal + view + { + if (instruction.executionParameters.gasLimit == 0) { + revert IWormholeRelayer.MaxTransactionFeeNotEnough(0); + } + if ( + 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) + { + 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.newMaximumRefundTarget = + calculateTargetRedeliveryMaximumRefund(send.targetChain, send.newMaxTransactionFee, relayProvider); + instruction.newReceiverValueTarget = + convertReceiverValueAmount(send.newReceiverValue, send.targetChain, relayProvider); + instruction.executionParameters = ExecutionParameters({ + version: 1, + gasLimit: calculateTargetGasRedeliveryAmount(send.targetChain, send.newMaxTransactionFee, relayProvider), + providerDeliveryAddress: relayProvider.getDeliveryAddress(send.targetChain) + }); + } + + function encodeRedeliveryInstruction(RedeliveryByTxHashInstruction memory instruction) + internal + pure + returns (bytes memory encoded) + { + encoded = abi.encodePacked( + instruction.payloadId, + instruction.sourceChain, + instruction.sourceTxHash, + instruction.sourceNonce, + instruction.targetChain, + instruction.deliveryIndex, + instruction.multisendIndex, + instruction.newMaximumRefundTarget, + instruction.newReceiverValueTarget, + instruction.executionParameters.version, + instruction.executionParameters.gasLimit, + instruction.executionParameters.providerDeliveryAddress + ); + } + + function encodeDeliveryInstructionsContainer(DeliveryInstructionsContainer memory container) + internal + pure + returns (bytes memory encoded) + { + encoded = abi.encodePacked( + container.payloadId, uint8(container.sufficientlyFunded ? 1 : 0), uint8(container.instructions.length) + ); + + for (uint256 i = 0; i < container.instructions.length; i++) { + encoded = abi.encodePacked(encoded, encodeDeliveryInstruction(container.instructions[i])); + } + } + + function encodeDeliveryInstruction(DeliveryInstruction memory instruction) + internal + pure + returns (bytes memory encoded) + { + encoded = abi.encodePacked( + instruction.targetChain, + instruction.targetAddress, + instruction.refundAddress, + instruction.maximumRefundTarget, + instruction.receiverValueTarget, + instruction.executionParameters.version, + instruction.executionParameters.gasLimit, + instruction.executionParameters.providerDeliveryAddress + ); + } + + /** + * Given a targetChain, maxTransactionFee, and a relay provider, this function calculates what the gas limit of the delivery transaction + * should be. + */ + function calculateTargetGasDeliveryAmount(uint16 targetChain, uint256 maxTransactionFee, IRelayProvider provider) + internal + view + returns (uint32 gasAmount) + { + gasAmount = calculateTargetGasDeliveryAmountHelper( + targetChain, maxTransactionFee, provider.quoteDeliveryOverhead(targetChain), provider + ); + } + + function calculateTargetDeliveryMaximumRefund( + uint16 targetChain, + uint256 maxTransactionFee, + IRelayProvider provider + ) internal view returns (uint256 maximumRefund) { + maximumRefund = calculateTargetDeliveryMaximumRefundHelper( + targetChain, maxTransactionFee, provider.quoteDeliveryOverhead(targetChain), provider + ); + } + + /** + * Given a targetChain, maxTransactionFee, and a relay provider, this function calculates what the gas limit of the redelivery transaction + * should be. + */ + function calculateTargetGasRedeliveryAmount(uint16 targetChain, uint256 maxTransactionFee, IRelayProvider provider) + internal + view + returns (uint32 gasAmount) + { + gasAmount = calculateTargetGasDeliveryAmountHelper( + targetChain, maxTransactionFee, provider.quoteRedeliveryOverhead(targetChain), provider + ); + } + + function calculateTargetRedeliveryMaximumRefund( + uint16 targetChain, + uint256 maxTransactionFee, + IRelayProvider provider + ) internal view returns (uint256 maximumRefund) { + maximumRefund = calculateTargetDeliveryMaximumRefundHelper( + targetChain, maxTransactionFee, provider.quoteRedeliveryOverhead(targetChain), provider + ); + } + + function calculateTargetGasDeliveryAmountHelper( + uint16 targetChain, + uint256 maxTransactionFee, + uint256 deliveryOverhead, + IRelayProvider provider + ) internal view returns (uint32 gasAmount) { + if (maxTransactionFee <= deliveryOverhead) { + gasAmount = 0; + } else { + uint256 gas = (maxTransactionFee - deliveryOverhead) / provider.quoteGasPrice(targetChain); + if (gas > type(uint32).max) { + gasAmount = type(uint32).max; + } else { + gasAmount = uint32(gas); + } + } + } + + function calculateTargetDeliveryMaximumRefundHelper( + uint16 targetChain, + uint256 maxTransactionFee, + uint256 deliveryOverhead, + IRelayProvider provider + ) internal view returns (uint256 maximumRefund) { + if (maxTransactionFee >= deliveryOverhead) { + uint256 remainder = maxTransactionFee - deliveryOverhead; + maximumRefund = assetConversionHelper(chainId(), remainder, targetChain, 1, 1, false, provider); + } else { + maximumRefund = 0; + } + } + + function assetConversionHelper( + uint16 sourceChain, + uint256 sourceAmount, + uint16 targetChain, + uint256 multiplier, + uint256 multiplierDenominator, + bool roundUp, + IRelayProvider provider + ) internal view returns (uint256 targetAmount) { + uint256 srcNativeCurrencyPrice = provider.quoteAssetPrice(sourceChain); + if (srcNativeCurrencyPrice == 0) { + revert IWormholeRelayer.RelayProviderDoesNotSupportTargetChain(); + } + + uint256 dstNativeCurrencyPrice = provider.quoteAssetPrice(targetChain); + if (dstNativeCurrencyPrice == 0) { + revert IWormholeRelayer.RelayProviderDoesNotSupportTargetChain(); + } + uint256 numerator = sourceAmount * srcNativeCurrencyPrice * multiplier; + uint256 denominator = dstNativeCurrencyPrice * multiplierDenominator; + if (roundUp) { + targetAmount = (numerator + denominator - 1) / denominator; + } else { + targetAmount = numerator / denominator; + } + } + + //This should invert quoteApplicationBudgetAmount, I.E when a user pays the sourceAmount, they receive at least the value of targetAmount they requested from + //quoteReceiverValue. + function convertReceiverValueAmount(uint256 sourceAmount, uint16 targetChain, IRelayProvider provider) + internal + view + returns (uint256 targetAmount) + { + (uint16 buffer, uint16 denominator) = provider.getAssetConversionBuffer(targetChain); + + targetAmount = assetConversionHelper( + chainId(), sourceAmount, targetChain, denominator, uint256(0) + denominator + buffer, false, provider + ); + } + + function decodeRedeliveryInstruction(bytes memory encoded) + public pure returns (RedeliveryByTxHashInstruction memory instruction) { uint256 index = 0; instruction.payloadId = encoded.toUint8(index); + if (instruction.payloadId != 2) { + revert InvalidPayloadId(instruction.payloadId); + } index += 1; instruction.sourceChain = encoded.toUint16(index); @@ -60,7 +340,7 @@ contract CoreRelayerMessages is CoreRelayerStructs, CoreRelayerGetters { } function decodeDeliveryInstructionsContainer(bytes memory encoded) - internal + public pure returns (DeliveryInstructionsContainer memory) { @@ -121,82 +401,4 @@ contract CoreRelayerMessages is CoreRelayerStructs, CoreRelayerGetters { instructions: instructionArray }); } - - function encodeMultichainSend(MultichainSend memory container) internal pure returns (bytes memory encoded) { - encoded = abi.encodePacked( - uint8(1), //version payload number - address(container.relayProviderAddress), - uint8(container.requests.length) //number of requests in the array - ); - - //Append all the messages to the array. - for (uint256 i = 0; i < container.requests.length; i++) { - Send memory request = container.requests[i]; - - encoded = abi.encodePacked( - encoded, - request.targetChain, - request.targetAddress, - request.refundAddress, - request.maxTransactionFee, - request.receiverValue, - uint8(request.relayParameters.length), - request.relayParameters - ); - } - } - - function decodeMultichainSend(bytes memory encoded) internal pure returns (MultichainSend memory) { - uint256 index = 0; - - uint8 payloadId = encoded.toUint8(index); - if (payloadId != 1) { - revert InvalidPayloadId(payloadId); - } - index += 1; - address relayProviderAddress = encoded.toAddress(index); - index += 20; - uint8 arrayLen = encoded.toUint8(index); - index += 1; - - Send[] memory requestArray = new Send[](arrayLen); - - for (uint8 i = 0; i < arrayLen; i++) { - Send memory request; - - // target chain of the delivery request - request.targetChain = encoded.toUint16(index); - index += 2; - - // target contract address - request.targetAddress = encoded.toBytes32(index); - index += 32; - - // address to send the refund to - request.refundAddress = encoded.toBytes32(index); - index += 32; - - request.maxTransactionFee = encoded.toUint256(index); - index += 32; - - request.receiverValue = encoded.toUint256(index); - index += 32; - - uint8 relayParametersLength = encoded.toUint8(index); - - index += 1; - - request.relayParameters = encoded.slice(index, relayParametersLength); - - index += relayParametersLength; - - requestArray[i] = request; - } - - if (index != encoded.length) { - revert InvalidSendsPayload(encoded.length); - } - - return MultichainSend({relayProviderAddress: relayProviderAddress, requests: requestArray}); - } } diff --git a/ethereum/contracts/coreRelayer/CoreRelayerSetters.sol b/ethereum/contracts/coreRelayer/CoreRelayerSetters.sol index 89301a0..f94a2fa 100644 --- a/ethereum/contracts/coreRelayer/CoreRelayerSetters.sol +++ b/ethereum/contracts/coreRelayer/CoreRelayerSetters.sol @@ -6,6 +6,7 @@ pragma solidity ^0.8.0; import "./CoreRelayerState.sol"; import "@openzeppelin/contracts/utils/Context.sol"; import "./CoreRelayerStructs.sol"; +import {IWormhole} from "../interfaces/IWormhole.sol"; contract CoreRelayerSetters is CoreRelayerState, Context { error InvalidEvmChainId(); @@ -34,6 +35,10 @@ 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; } @@ -42,12 +47,12 @@ contract CoreRelayerSetters is CoreRelayerState, Context { _state.registeredCoreRelayerContract[chainId] = relayerAddress; } - function setForwardingRequest(CoreRelayerStructs.ForwardingRequest memory request) internal { - _state.forwardingRequest = request; + function setForwardInstruction(CoreRelayerStructs.ForwardInstruction memory request) internal { + _state.forwardInstruction = request; } - function clearForwardingRequest() internal { - delete _state.forwardingRequest; //TODO is this the best way to accomplish this? + function clearForwardInstruction() internal { + delete _state.forwardInstruction; } function setContractLock(bool status) internal { diff --git a/ethereum/contracts/coreRelayer/CoreRelayerSetup.sol b/ethereum/contracts/coreRelayer/CoreRelayerSetup.sol index 900aaf2..b5c3318 100644 --- a/ethereum/contracts/coreRelayer/CoreRelayerSetup.sol +++ b/ethereum/contracts/coreRelayer/CoreRelayerSetup.sol @@ -43,8 +43,6 @@ contract CoreRelayerSetup is CoreRelayerSetters, ERC1967Upgrade { setGovernanceContract(governanceContract); setEvmChainId(evmChainId); - //setRegisteredCoreRelayerContract(chainId, bytes32(uint256(uint160(address(this))))); - _upgradeTo(implementation); // call initialize function of the new implementation diff --git a/ethereum/contracts/coreRelayer/CoreRelayerState.sol b/ethereum/contracts/coreRelayer/CoreRelayerState.sol index 0eee859..29b21cf 100644 --- a/ethereum/contracts/coreRelayer/CoreRelayerState.sol +++ b/ethereum/contracts/coreRelayer/CoreRelayerState.sol @@ -9,6 +9,7 @@ contract CoreRelayerStorage { struct Provider { uint16 chainId; address payable wormhole; + uint256 wormholeMessageFee; uint16 governanceChainId; bytes32 governanceContract; } @@ -26,7 +27,7 @@ contract CoreRelayerStorage { // address of the default relay provider on this chain address defaultRelayProvider; // Request which will be forwarded from the current delivery. - CoreRelayerStructs.ForwardingRequest forwardingRequest; + CoreRelayerStructs.ForwardInstruction forwardInstruction; // mapping of initialized implementations mapping(address => bool) initializedImplementations; // mapping of relayer contracts on other chains diff --git a/ethereum/contracts/coreRelayer/CoreRelayerStructs.sol b/ethereum/contracts/coreRelayer/CoreRelayerStructs.sol index 890254d..76f6052 100644 --- a/ethereum/contracts/coreRelayer/CoreRelayerStructs.sol +++ b/ethereum/contracts/coreRelayer/CoreRelayerStructs.sol @@ -3,74 +3,9 @@ pragma solidity ^0.8.0; -import "../interfaces/IWormhole.sol"; +import "../interfaces/IWormholeRelayer.sol"; abstract contract CoreRelayerStructs { - //This first group of structs are external facing API objects, - //which should be considered untrusted and unmodifiable - - struct MultichainSend { - address relayProviderAddress; - Send[] requests; - } - - // struct TargetDeliveryParameters { - // // encoded batchVM to be delivered on the target chain - // bytes encodedVM; - // // Index of the delivery VM in a batch - // uint8 deliveryIndex; - // uint8 multisendIndex; - // //uint32 targetCallGasOverride; - // } - - struct TargetDeliveryParametersSingle { - // encoded batchVM to be delivered on the target chain - bytes[] encodedVMs; - // Index of the delivery VM in a batch - uint8 deliveryIndex; - // Index of the target chain inside the delivery VM - uint8 multisendIndex; - // relayer refund address - address payable relayerRefundAddress; - } - // Optional gasOverride which can be supplied by the relayer - // uint32 targetCallGasOverride; - - struct TargetRedeliveryByTxHashParamsSingle { - bytes redeliveryVM; - bytes[] sourceEncodedVMs; - address payable relayerRefundAddress; - } - - struct Send { - uint16 targetChain; - bytes32 targetAddress; - bytes32 refundAddress; - uint256 maxTransactionFee; - uint256 receiverValue; - bytes relayParameters; - } - - struct ResendByTx { - uint16 sourceChain; - bytes32 sourceTxHash; - uint32 sourceNonce; - uint16 targetChain; - uint8 deliveryIndex; - uint8 multisendIndex; - uint256 newMaxTransactionFee; - uint256 newReceiverValue; - bytes newRelayParameters; - } - - struct RelayParameters { - uint8 version; //1 - bytes32 providerAddressOverride; - } - - //Below this are internal structs - - //Wire Types struct DeliveryInstructionsContainer { uint8 payloadId; //1 bool sufficientlyFunded; @@ -83,7 +18,7 @@ abstract contract CoreRelayerStructs { bytes32 refundAddress; uint256 maximumRefundTarget; uint256 receiverValueTarget; - ExecutionParameters executionParameters; //Has the gas limit to execute with + ExecutionParameters executionParameters; } struct ExecutionParameters { @@ -105,23 +40,13 @@ abstract contract CoreRelayerStructs { ExecutionParameters executionParameters; } - //End Wire Types - - //Internal usage structs - - struct AllowedEmitterSequence { - // wormhole emitter address - bytes32 emitterAddress; - // wormhole message sequence - uint64 sequence; - } - - struct ForwardingRequest { - bytes deliveryRequestsContainer; - uint16 rolloverChain; + struct ForwardInstruction { + DeliveryInstructionsContainer container; uint32 nonce; address sender; uint256 msgValue; + uint256 totalFee; + address relayProvider; bool isValid; } } diff --git a/ethereum/contracts/interfaces/IDelivery.sol b/ethereum/contracts/interfaces/IDelivery.sol index dfdb636..a5f0bde 100644 --- a/ethereum/contracts/interfaces/IDelivery.sol +++ b/ethereum/contracts/interfaces/IDelivery.sol @@ -30,9 +30,10 @@ interface IDelivery { error InvalidEmitterInRedeliveryVM(); error MismatchingRelayProvidersInRedelivery(); // The same relay provider must be specified when doing a single VAA redeliver error UnexpectedRelayer(); // msg.sender must be the provider - error InvalidVaa(uint8 index); + error InvalidVaa(uint8 index, string reason); error InvalidEmitter(); error SendNotSufficientlyFunded(); // This delivery request was not sufficiently funded, and must request redelivery error InsufficientRelayerFunds(); // The relayer didn't pass sufficient funds (msg.value does not cover the necessary budget fees) error TargetChainIsNotThisChain(uint16 targetChainId); + error ReentrantCall(); // A delivery cannot occur during another delivery } diff --git a/ethereum/contracts/interfaces/IWormholeRelayer.sol b/ethereum/contracts/interfaces/IWormholeRelayer.sol index 8cbef9f..6498f18 100644 --- a/ethereum/contracts/interfaces/IWormholeRelayer.sol +++ b/ethereum/contracts/interfaces/IWormholeRelayer.sol @@ -263,13 +263,13 @@ interface IWormholeRelayer { * 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 rolloverChain If LEFTOVER_VALUE > NEEDED_VALUE, then the maxTransactionFee of one of the requests in the array of sends will be incremented by 'LEFTOVER_VALUE - NEEDED_VALUE' - * Specifically, the 'send' that will have it's maxTransactionFee incremented is the first send in the 'requests.requests' array that has targetChain equal to 'rolloverChain' * @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, uint16 rolloverChain, uint32 nonce) external payable; + 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 @@ -354,15 +354,13 @@ interface IWormholeRelayer { */ function getDefaultRelayParams() external pure returns (bytes memory relayParams); - error FundsTooMuch(); // (maxTransactionFee, converted to target chain currency) + (receiverValue, converted to target chain currency) is greater than what your chosen relay provider allows - error MaxTransactionFeeNotEnough(); // maxTransactionFee is less than the minimum needed by your chosen relay provider + error FundsTooMuch(uint8 multisendIndex); // (maxTransactionFee, converted to target chain currency) + (receiverValue, converted to target chain currency) is greater than what your chosen relay provider allows + error MaxTransactionFeeNotEnough(uint8 multisendIndex); // maxTransactionFee is less than the minimum needed by your chosen relay provider error MsgValueTooLow(); // msg.value is too low // Specifically, (msg.value) + (any leftover funds if this is a forward) is less than (maxTransactionFee + receiverValue), summed over all of your requests if this is a multichainSend/multichainForward error NonceIsZero(); // Nonce cannot be 0 - error NoDeliveryInProcess(); // Forwards can only be requested within execution of 'receiveWormholeMessages', or when a delivery is in progress + error NoDeliveryInProgress(); // Forwards can only be requested within execution of 'receiveWormholeMessages', or when a delivery is in progress error MultipleForwardsRequested(); // Only one forward can be requested in a transaction + error ForwardRequestFromWrongAddress(); // A forward was requested from an address that is not the 'targetAddress' of the original delivery error RelayProviderDoesNotSupportTargetChain(); // Your relay provider does not support the target chain you specified - error RolloverChainNotIncluded(); // None of the Send structs in your multiForward are for the target chain 'rolloverChain' - error ChainNotFoundInSends(uint16 chainId); // This should never happen. Post a Github Issue if this occurs - error ReentrantCall(); // A delivery cannot occur during another delivery } diff --git a/ethereum/contracts/mock/MockRelayerIntegration.sol b/ethereum/contracts/mock/MockRelayerIntegration.sol index 9de6b1b..110f360 100644 --- a/ethereum/contracts/mock/MockRelayerIntegration.sol +++ b/ethereum/contracts/mock/MockRelayerIntegration.sol @@ -174,7 +174,7 @@ contract MockRelayerIntegration is IWormholeReceiver { relayProviderAddress: relayer.getDefaultRelayProvider() }); - relayer.multichainForward(container, sendRequests[0].targetChain, parsed.nonce); + relayer.multichainForward(container, parsed.nonce); } } diff --git a/ethereum/forge-test/CoreRelayer.t.sol b/ethereum/forge-test/CoreRelayer.t.sol index f982e8b..de7c73c 100644 --- a/ethereum/forge-test/CoreRelayer.t.sol +++ b/ethereum/forge-test/CoreRelayer.t.sol @@ -10,6 +10,7 @@ import {RelayProviderProxy} from "../contracts/relayProvider/RelayProviderProxy. import {RelayProviderMessages} from "../contracts/relayProvider/RelayProviderMessages.sol"; import {RelayProviderStructs} from "../contracts/relayProvider/RelayProviderStructs.sol"; import {IWormholeRelayer} from "../contracts/interfaces/IWormholeRelayer.sol"; +import {IDelivery} from "../contracts/interfaces/IDelivery.sol"; import {CoreRelayer} from "../contracts/coreRelayer/CoreRelayer.sol"; import {CoreRelayerStructs} from "../contracts/coreRelayer/CoreRelayerStructs.sol"; import {CoreRelayerSetup} from "../contracts/coreRelayer/CoreRelayerSetup.sol"; @@ -404,6 +405,9 @@ contract TestCoreRelayer is Test { uint256 relayerProfit = uint256(feeParams.sourceNativePrice) * (setup.source.rewardAddress.balance - rewardAddressBalance) - feeParams.targetNativePrice * (relayerBalance - setup.target.relayer.balance); + console.log(USDcost); + console.log(relayerProfit); + console.log((USDcost - relayerProfit)); assertTrue(USDcost == relayerProfit, "We paid the exact amount"); } @@ -774,8 +778,8 @@ contract TestCoreRelayer is Test { IWormhole.VM parsed; uint256 budget; IWormholeRelayer.ResendByTx redeliveryRequest; - CoreRelayer.TargetDeliveryParametersSingle originalDelivery; - CoreRelayerStructs.TargetRedeliveryByTxHashParamsSingle package; + IDelivery.TargetDeliveryParametersSingle originalDelivery; + IDelivery.TargetRedeliveryByTxHashParamsSingle package; CoreRelayer.RedeliveryByTxHashInstruction instruction; } @@ -871,12 +875,12 @@ contract TestCoreRelayer is Test { invalidateVM(fakeVM, setup.target.wormholeSimulator); stack.originalDelivery.encodedVMs[2] = fakeVM; - stack.package = CoreRelayerStructs.TargetRedeliveryByTxHashParamsSingle( + stack.package = IDelivery.TargetRedeliveryByTxHashParamsSingle( stack.redeliveryVM, stack.originalDelivery.encodedVMs, payable(setup.target.relayer) ); stack.parsed = relayerWormhole.parseVM(stack.redeliveryVM); - stack.instruction = setup.target.coreRelayerFull.getRedeliveryByTxHashInstruction(stack.parsed.payload); + stack.instruction = setup.target.coreRelayerFull.decodeRedeliveryInstruction(stack.parsed.payload); stack.budget = stack.instruction.newMaximumRefundTarget + stack.instruction.newReceiverValueTarget + setup.target.wormhole.messageFee(); @@ -884,12 +888,12 @@ contract TestCoreRelayer is Test { vm.deal(setup.target.relayer, stack.budget); vm.prank(setup.target.relayer); - vm.expectRevert(abi.encodeWithSignature("InvalidVaa(uint8)", 2)); + vm.expectRevert(abi.encodeWithSignature("InvalidVaa(uint8,string)", 2, "")); setup.target.coreRelayerFull.redeliverSingle{value: stack.budget}(stack.package); stack.originalDelivery.encodedVMs[2] = stack.originalDelivery.encodedVMs[0]; - stack.package = CoreRelayerStructs.TargetRedeliveryByTxHashParamsSingle( + stack.package = IDelivery.TargetRedeliveryByTxHashParamsSingle( stack.redeliveryVM, stack.originalDelivery.encodedVMs, payable(setup.target.relayer) ); @@ -899,7 +903,7 @@ contract TestCoreRelayer is Test { stack.originalDelivery.encodedVMs[2] = correctVM; - stack.package = CoreRelayerStructs.TargetRedeliveryByTxHashParamsSingle( + stack.package = IDelivery.TargetRedeliveryByTxHashParamsSingle( stack.redeliveryVM, stack.originalDelivery.encodedVMs, payable(setup.target.relayer) ); @@ -907,7 +911,7 @@ contract TestCoreRelayer is Test { fakeVM = abi.encodePacked(correctVM); invalidateVM(fakeVM, setup.target.wormholeSimulator); - stack.package = CoreRelayerStructs.TargetRedeliveryByTxHashParamsSingle( + stack.package = IDelivery.TargetRedeliveryByTxHashParamsSingle( fakeVM, stack.originalDelivery.encodedVMs, payable(setup.target.relayer) ); @@ -918,7 +922,7 @@ contract TestCoreRelayer is Test { fakeVM = relayerWormholeSimulator.fetchSignedMessageFromLogs( stack.entries[0], setup.sourceChainId, address(setup.source.integration) ); - stack.package = CoreRelayerStructs.TargetRedeliveryByTxHashParamsSingle( + stack.package = IDelivery.TargetRedeliveryByTxHashParamsSingle( fakeVM, stack.originalDelivery.encodedVMs, payable(setup.target.relayer) ); @@ -940,7 +944,7 @@ contract TestCoreRelayer is Test { fakeVM = relayerWormholeSimulator.fetchSignedMessageFromLogs( stack.entries[0], setup.sourceChainId, address(setup.source.coreRelayer) ); - stack.package = CoreRelayerStructs.TargetRedeliveryByTxHashParamsSingle( + stack.package = IDelivery.TargetRedeliveryByTxHashParamsSingle( fakeVM, stack.originalDelivery.encodedVMs, payable(setup.target.relayer) ); @@ -948,7 +952,7 @@ contract TestCoreRelayer is Test { vm.expectRevert(abi.encodeWithSignature("MismatchingRelayProvidersInRedelivery()")); setup.target.coreRelayerFull.redeliverSingle{value: stack.budget}(stack.package); - stack.package = CoreRelayerStructs.TargetRedeliveryByTxHashParamsSingle( + stack.package = IDelivery.TargetRedeliveryByTxHashParamsSingle( stack.redeliveryVM, stack.originalDelivery.encodedVMs, payable(msg.sender) ); @@ -992,7 +996,6 @@ contract TestCoreRelayer is Test { newRelayParameters: setup.source.coreRelayer.getDefaultRelayParams() }); setup.source.relayProvider.updatePrice(differentChainId, gasParams.targetGasPrice, feeParams.targetNativePrice); - setup.source.relayProvider.updatePrice(differentChainId, gasParams.sourceGasPrice, feeParams.sourceNativePrice); setup.source.relayProvider.updateDeliveryAddress( differentChainId, bytes32(uint256(uint160(address(setup.target.relayer)))) ); @@ -1009,12 +1012,13 @@ contract TestCoreRelayer is Test { fakeVM = relayerWormholeSimulator.fetchSignedMessageFromLogs( stack.entries[0], setup.sourceChainId, address(setup.source.coreRelayer) ); - stack.package = CoreRelayerStructs.TargetRedeliveryByTxHashParamsSingle( + stack.package = IDelivery.TargetRedeliveryByTxHashParamsSingle( fakeVM, stack.originalDelivery.encodedVMs, payable(setup.target.relayer) ); redeliveryVmHash = relayerWormhole.parseVM(fakeVM).hash; - uint256 txValue = stack.payment + map[differentChainId].wormhole.messageFee(); + uint256 txValue = stack.payment * feeParams.sourceNativePrice / feeParams.targetNativePrice + 1 + + map[differentChainId].wormhole.messageFee(); vm.deal(setup.target.relayer, txValue); vm.expectEmit(true, true, true, true, address(map[differentChainId].coreRelayer)); @@ -1028,7 +1032,7 @@ contract TestCoreRelayer is Test { vm.prank(setup.target.relayer); map[differentChainId].coreRelayerFull.redeliverSingle{value: txValue}(stack.package); - stack.package = CoreRelayerStructs.TargetRedeliveryByTxHashParamsSingle({ + stack.package = IDelivery.TargetRedeliveryByTxHashParamsSingle({ redeliveryVM: correctVM, sourceEncodedVMs: stack.originalDelivery.encodedVMs, relayerRefundAddress: payable(setup.target.relayer) @@ -1063,7 +1067,7 @@ contract TestCoreRelayer is Test { bytes[] encodedVMs; IWormhole.VM parsed; uint256 budget; - CoreRelayer.TargetDeliveryParametersSingle package; + IDelivery.TargetDeliveryParametersSingle package; CoreRelayer.DeliveryInstruction instruction; } @@ -1111,7 +1115,7 @@ contract TestCoreRelayer is Test { stack.encodedVMs[1] = stack.actualVM2; stack.encodedVMs[2] = stack.deliveryVM; - stack.package = CoreRelayerStructs.TargetDeliveryParametersSingle({ + stack.package = IDelivery.TargetDeliveryParametersSingle({ encodedVMs: stack.encodedVMs, deliveryIndex: 2, multisendIndex: 0, @@ -1120,7 +1124,7 @@ contract TestCoreRelayer is Test { stack.parsed = relayerWormhole.parseVM(stack.deliveryVM); stack.instruction = - setup.target.coreRelayerFull.getDeliveryInstructionsContainer(stack.parsed.payload).instructions[0]; + setup.target.coreRelayerFull.decodeDeliveryInstructionsContainer(stack.parsed.payload).instructions[0]; stack.budget = stack.instruction.maximumRefundTarget + stack.instruction.receiverValueTarget + setup.source.wormhole.messageFee(); @@ -1178,7 +1182,7 @@ contract TestCoreRelayer is Test { stack.encodedVMs[1] = stack.actualVM2; stack.encodedVMs[2] = fakeVM; - stack.package = CoreRelayerStructs.TargetDeliveryParametersSingle({ + stack.package = IDelivery.TargetDeliveryParametersSingle({ encodedVMs: stack.encodedVMs, deliveryIndex: 2, multisendIndex: 0, @@ -1187,18 +1191,18 @@ contract TestCoreRelayer is Test { stack.parsed = relayerWormhole.parseVM(stack.deliveryVM); stack.instruction = - setup.target.coreRelayerFull.getDeliveryInstructionsContainer(stack.parsed.payload).instructions[0]; + setup.target.coreRelayerFull.decodeDeliveryInstructionsContainer(stack.parsed.payload).instructions[0]; stack.budget = stack.instruction.maximumRefundTarget + stack.instruction.receiverValueTarget + setup.target.wormhole.messageFee(); vm.prank(setup.target.relayer); - vm.expectRevert(abi.encodeWithSignature("InvalidVaa(uint8)", 2)); + vm.expectRevert(abi.encodeWithSignature("InvalidVaa(uint8,string)", 2, "")); setup.target.coreRelayerFull.deliverSingle{value: stack.budget}(stack.package); stack.encodedVMs[2] = stack.encodedVMs[0]; - stack.package = CoreRelayerStructs.TargetDeliveryParametersSingle({ + stack.package = IDelivery.TargetDeliveryParametersSingle({ encodedVMs: stack.encodedVMs, deliveryIndex: 2, multisendIndex: 0, @@ -1211,7 +1215,7 @@ contract TestCoreRelayer is Test { stack.encodedVMs[2] = stack.deliveryVM; - stack.package = CoreRelayerStructs.TargetDeliveryParametersSingle({ + stack.package = IDelivery.TargetDeliveryParametersSingle({ encodedVMs: stack.encodedVMs, deliveryIndex: 2, multisendIndex: 0, @@ -1291,8 +1295,10 @@ contract TestCoreRelayer is Test { relayParameters: setup.source.coreRelayer.getDefaultRelayParams() }); - vm.expectRevert(abi.encodeWithSignature("MaxTransactionFeeNotEnough()")); - setup.source.coreRelayer.send{value: stack.deliveryOverhead - 1}( + uint256 wormholeFee = setup.source.wormhole.messageFee(); + + vm.expectRevert(abi.encodeWithSignature("MaxTransactionFeeNotEnough(uint8)", 0)); + setup.source.coreRelayer.send{value: stack.deliveryOverhead - 1 + wormholeFee}( stack.badSend, 1, address(setup.source.relayProvider) ); @@ -1302,7 +1308,7 @@ contract TestCoreRelayer is Test { setup.targetChainId, uint256(gasParams.targetGasLimit - 1) * gasParams.targetGasPrice ); - vm.expectRevert(abi.encodeWithSignature("FundsTooMuch()")); + vm.expectRevert(abi.encodeWithSignature("FundsTooMuch(uint8)", 0)); setup.source.coreRelayer.send{value: stack.payment}( stack.deliveryRequest, 1, address(setup.source.relayProvider) ); @@ -1318,7 +1324,7 @@ contract TestCoreRelayer is Test { mapping(uint256 => bool) nonceCompleted; - mapping(bytes32 => CoreRelayer.TargetDeliveryParametersSingle) pastDeliveries; + mapping(bytes32 => IDelivery.TargetDeliveryParametersSingle) pastDeliveries; function genericRelayer(uint16 chainId, uint8 num) internal { Vm.Log[] memory entries = truncateRecordedLogs(chainId, num); @@ -1395,20 +1401,19 @@ contract TestCoreRelayer is Test { function genericRelay( Contracts memory contracts, uint8 counter, - bytes memory encodedDeliveryInstructionContainer, + bytes memory encodedDeliveryInstructionsContainer, bytes[] memory encodedVMsToBeDelivered, IWormhole.VM memory parsedInstruction ) internal { uint8 payloadId = parsedInstruction.payload.toUint8(0); if (payloadId == 1) { CoreRelayer.DeliveryInstructionsContainer memory container = - contracts.coreRelayerFull.getDeliveryInstructionsContainer(parsedInstruction.payload); + contracts.coreRelayerFull.decodeDeliveryInstructionsContainer(parsedInstruction.payload); for (uint8 k = 0; k < container.instructions.length; k++) { uint256 budget = container.instructions[k].maximumRefundTarget + container.instructions[k].receiverValueTarget; uint16 targetChain = container.instructions[k].targetChain; - CoreRelayer.TargetDeliveryParametersSingle memory package = CoreRelayerStructs - .TargetDeliveryParametersSingle({ + IDelivery.TargetDeliveryParametersSingle memory package = IDelivery.TargetDeliveryParametersSingle({ encodedVMs: encodedVMsToBeDelivered, deliveryIndex: counter, multisendIndex: k, @@ -1421,15 +1426,15 @@ contract TestCoreRelayer is Test { } } else if (payloadId == 2) { CoreRelayer.RedeliveryByTxHashInstruction memory instruction = - contracts.coreRelayerFull.getRedeliveryByTxHashInstruction(parsedInstruction.payload); - CoreRelayer.TargetDeliveryParametersSingle memory originalDelivery = + contracts.coreRelayerFull.decodeRedeliveryInstruction(parsedInstruction.payload); + IDelivery.TargetDeliveryParametersSingle memory originalDelivery = pastDeliveries[keccak256(abi.encodePacked(instruction.sourceTxHash, instruction.multisendIndex))]; uint16 targetChain = instruction.targetChain; uint256 budget = instruction.newMaximumRefundTarget + instruction.newReceiverValueTarget + map[targetChain].wormhole.messageFee(); - CoreRelayerStructs.TargetRedeliveryByTxHashParamsSingle memory package = CoreRelayerStructs + IDelivery.TargetRedeliveryByTxHashParamsSingle memory package = IDelivery .TargetRedeliveryByTxHashParamsSingle({ - redeliveryVM: encodedDeliveryInstructionContainer, + redeliveryVM: encodedDeliveryInstructionsContainer, sourceEncodedVMs: originalDelivery.encodedVMs, relayerRefundAddress: payable(map[targetChain].relayer) }); diff --git a/ethereum/ts-test/2_core_relayer.ts b/ethereum/ts-test/2_core_relayer.ts index 0c95bcd..22726b1 100644 --- a/ethereum/ts-test/2_core_relayer.ts +++ b/ethereum/ts-test/2_core_relayer.ts @@ -14,7 +14,7 @@ import { loadCoreRelayers, loadMockIntegrations, } from "../ts-scripts/helpers/env" -import { MockRelayerIntegration, CoreRelayerStructs } from "../../sdk/src" +import { MockRelayerIntegration, IWormholeRelayer } from "../../sdk/src" const ETHEREUM_ROOT = `${__dirname}/..` init() @@ -314,7 +314,7 @@ describe("Core Relayer Integration Test - Two Chains", () => { expect(message).to.not.equal(arbitraryPayload) console.log("Resending the message"); - const request: CoreRelayerStructs.ResendByTxStruct = { + const request: IWormholeRelayer.ResendByTxStruct = { sourceChain: sourceChain.chainId, sourceTxHash: tx.hash, sourceNonce: 1, @@ -402,7 +402,7 @@ describe("Core Relayer Integration Test - Two Chains", () => { // RESEND THE MESSAGE SOMEHOW! /*console.log("Resending the message"); - const request: CoreRelayerStructs.ResendByTxStruct = { + const request: IWormholeRelayer.ResendByTxStruct = { sourceChain: sourceChain.chainId, sourceTxHash: tx.hash, sourceNonce: 1, diff --git a/relayer_engine/pkgs/sdk/src/index.ts b/relayer_engine/pkgs/sdk/src/index.ts index f3fc993..ada48c7 100644 --- a/relayer_engine/pkgs/sdk/src/index.ts +++ b/relayer_engine/pkgs/sdk/src/index.ts @@ -6,4 +6,6 @@ export type { IWormhole, LogMessagePublishedEvent } from "./ethers-contracts/IWo export { IWormhole__factory } from "./ethers-contracts/factories/IWormhole__factory" export type { RelayProvider } from "./ethers-contracts/RelayProvider" export { RelayProvider__factory } from "./ethers-contracts/factories/RelayProvider__factory" +export type {IDelivery} from "./ethers-contracts/IDelivery" +export type {IWormholeRelayer} from "./ethers-contracts/IWormholeRelayer" export * from './structs' \ No newline at end of file diff --git a/relayer_engine/src/plugin/src/plugin.ts b/relayer_engine/src/plugin/src/plugin.ts index afd602a..e1f2803 100644 --- a/relayer_engine/src/plugin/src/plugin.ts +++ b/relayer_engine/src/plugin/src/plugin.ts @@ -24,7 +24,7 @@ import { IWormhole__factory, RelayProvider__factory, LogMessagePublishedEvent, - CoreRelayerStructs, + IDelivery, DeliveryInstructionsContainer, parseDeliveryInstructionsContainer, parseRedeliveryByTxHashInstruction, @@ -545,7 +545,7 @@ export class GenericRelayerPlugin implements Plugin { wallet ) - const input: CoreRelayerStructs.TargetDeliveryParametersSingleStruct = { + const input: IDelivery.TargetDeliveryParametersSingleStruct = { encodedVMs: payload.vaas, deliveryIndex: payload.deliveryVaaIndex, multisendIndex: i, @@ -597,7 +597,7 @@ export class GenericRelayerPlugin implements Plugin { const { newReceiverValueTarget, newMaximumRefundTarget } = redelivery.ix const budget = newReceiverValueTarget.add(newMaximumRefundTarget).add(100) - const input: CoreRelayerStructs.TargetRedeliveryByTxHashParamsSingleStruct = { + const input: IDelivery.TargetRedeliveryByTxHashParamsSingleStruct = { sourceEncodedVMs: payload.vaas, redeliveryVM: redelivery.vaa.bytes, relayerRefundAddress: wallet.address, diff --git a/sdk/src/index.ts b/sdk/src/index.ts index f3fc993..ada48c7 100644 --- a/sdk/src/index.ts +++ b/sdk/src/index.ts @@ -6,4 +6,6 @@ export type { IWormhole, LogMessagePublishedEvent } from "./ethers-contracts/IWo export { IWormhole__factory } from "./ethers-contracts/factories/IWormhole__factory" export type { RelayProvider } from "./ethers-contracts/RelayProvider" export { RelayProvider__factory } from "./ethers-contracts/factories/RelayProvider__factory" +export type {IDelivery} from "./ethers-contracts/IDelivery" +export type {IWormholeRelayer} from "./ethers-contracts/IWormholeRelayer" export * from './structs' \ No newline at end of file