// contracts/Bridge.sol // SPDX-License-Identifier: Apache 2 pragma solidity ^0.8.0; import "../libraries/external/BytesLib.sol"; import "../interfaces/IWormholeReceiver.sol"; import "./CoreRelayerGovernance.sol"; import "./CoreRelayerStructs.sol"; contract CoreRelayer is CoreRelayerGovernance { using BytesLib for bytes; enum DeliveryStatus { SUCCESS, RECEIVER_FAILURE, FORWARD_REQUEST_FAILURE, FORWARD_REQUEST_SUCCESS, INVALID_REDELIVERY } event Delivery( address indexed recipientContract, uint16 indexed sourceChain, uint64 indexed sequence, bytes32 deliveryVaaHash, DeliveryStatus status ); 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) 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); } 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 resend(ResendByTx memory request, uint32 nonce, IRelayProvider provider) 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 }) ); if (!isSufficient) { if (reason == 26) { revert MaxTransactionFeeNotEnough(); } else { revert FundsTooMuch(); } } uint256 totalFee = requestFee + wormhole().messageFee(); //Make sure the msg.value covers the budget they specified if (msg.value < totalFee) { revert MsgValueTooLow(); } sequence = emitRedelivery(request, nonce, provider.getConsistencyLevel(), receiverValueTarget, maximumRefund, provider); //Send the delivery fees to the specified address of the provider. provider.getRewardAddress().call{value: msg.value - wormhole().messageFee()}(""); } function emitRedelivery( ResendByTx memory request, uint32 nonce, uint8 consistencyLevel, uint256 receiverValueTarget, uint256 maximumRefund, IRelayProvider provider ) internal returns (uint64 sequence) { bytes memory instruction = convertToEncodedRedeliveryByTxHashInstruction( request, receiverValueTarget, maximumRefund, calculateTargetGasRedeliveryAmount(request.targetChain, request.newMaxTransactionFee, provider), provider ); sequence = wormhole().publishMessage{value: wormhole().messageFee()}(nonce, instruction, consistencyLevel); } /** * TODO: Correct this comment * @dev `multisend` generates a VAA with DeliveryInstructions to be delivered to the specified target * contract based on user parameters. * it parses the RelayParameters to determine the target chain ID * it estimates the cost of relaying the batch * it confirms that the user has passed enough value to pay the relayer * it checks that the passed nonce is not zero (VAAs with a nonce of zero will not be batched) * it generates a VAA with the encoded DeliveryInstructions */ function multichainSend(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(); } } if (nonce == 0) { revert NonceIsZero(); } // encode the DeliveryInstructions bytes memory container = convertToEncodedDeliveryInstructions(deliveryRequests, true); // emit delivery message IWormhole wormhole = wormhole(); IRelayProvider provider = IRelayProvider(deliveryRequests.relayProviderAddress); sequence = wormhole.publishMessage{value: wormhole.messageFee()}(nonce, container, provider.getConsistencyLevel()); //pay fee to provider provider.getRewardAddress().call{value: totalCost - wormhole.messageFee()}(""); } /** * TODO correct this comment * @dev `forward` queues up a 'send' which will be executed after the present delivery is complete * & uses the gas refund to cover the costs. * contract based on user parameters. * it parses the RelayParameters to determine the target chain ID * it estimates the cost of relaying the batch * it confirms that the user has passed enough value to pay the relayer * it checks that the passed nonce is not zero (VAAs with a nonce of zero will not be batched) * it generates a VAA with the encoded DeliveryInstructions */ function multichainForward(MultichainSend memory deliveryRequests, uint16 rolloverChain, uint32 nonce) public payable { // Can only forward while a delivery is in process. if (!isContractLocked()) { revert NoDeliveryInProcess(); } if (getForwardingRequest().isValid) { revert CantRequestMultipleForwards(); } //We want to catch malformed requests in this function, and only underfunded requests when emitting. verifyForwardingRequest(deliveryRequests, rolloverChain, nonce); bytes memory encodedMultichainSend = encodeMultichainSend(deliveryRequests); setForwardingRequest( ForwardingRequest({ deliveryRequestsContainer: encodedMultichainSend, rolloverChain: rolloverChain, nonce: nonce, msgValue: msg.value, sender: msg.sender, isValid: true }) ); } function emitForward(uint256 refundAmount, ForwardingRequest memory forwardingRequest) internal returns (uint64, bool) { MultichainSend memory container = decodeMultichainSend(forwardingRequest.deliveryRequestsContainer); //Add any additional funds which were passed in to the refund amount refundAmount = refundAmount + forwardingRequest.msgValue; //make sure the refund amount covers the native gas amounts (uint256 totalMinimumFees, bool funded,) = sufficientFundsHelper(container, refundAmount); //REVISE consider deducting the cost of this process from the refund amount? 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; } //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() ); // if funded, pay out reward to provider. Otherwise, the delivery code will handle sending a refund. if (funded) { provider.getRewardAddress().call{value: refundAmount}(""); } //clear forwarding request from cache clearForwardingRequest(); return (sequence, funded); } 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++) { 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) internal pure returns (uint16 deliveryRequestIndex) { 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; } } function _executeDelivery( IWormhole wormhole, DeliveryInstruction memory internalInstruction, bytes[] memory encodedVMs, bytes32 deliveryVaaHash, address payable relayerRefund, uint16 sourceChain, uint64 sourceSequence ) internal { //REVISE Decide whether we want to remove the DeliveryInstructionContainer from encodedVMs. // lock the contract to prevent reentrancy if (isContractLocked()) { revert ReentrantCall(); } setContractLock(true); setLockedTargetAddress(fromWormholeFormat(internalInstruction.targetAddress)); // store gas budget pre target invocation to calculate unused gas budget uint256 preGas = gasleft(); // call the receiveWormholeMessages endpoint on the target contract (bool success,) = fromWormholeFormat(internalInstruction.targetAddress).call{ gas: internalInstruction.executionParameters.gasLimit, value: internalInstruction.receiverValueTarget }(abi.encodeCall(IWormholeReceiver.receiveWormholeMessages, (encodedVMs, new bytes[](0)))); uint256 postGas = gasleft(); // There's no easy way to measure the exact cost of the CALL instruction. // This is due to the fact that the compiler probably emits DUPN or MSTORE instructions // to setup the arguments for the call just after our measurement. // This means the refund could be off by a few units of gas. // Thus, we ensure the overhead doesn't cause an overflow in our refund formula here. uint256 gasUsed = (preGas - postGas) > internalInstruction.executionParameters.gasLimit ? internalInstruction.executionParameters.gasLimit : (preGas - postGas); // refund unused gas budget uint256 weiToRefund = internalInstruction.receiverValueTarget; if (success) { weiToRefund = (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(); 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,) = fromWormholeFormat(internalInstruction.refundAddress).call{value: 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 }); } } else { (bool sent,) = fromWormholeFormat(internalInstruction.refundAddress).call{value: 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 }); } } uint256 receiverValuePaid = (success ? internalInstruction.receiverValueTarget : 0); uint256 wormholeFeePaid = forwardingRequest.isValid ? wormhole.messageFee() : 0; uint256 relayerRefundAmount = msg.value - weiToRefund - receiverValuePaid - wormholeFeePaid; // refund the rest to relayer relayerRefund.call{value: 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; } function getDefaultRelayProvider() public view returns (IRelayProvider) { return defaultRelayProvider(); } function redeliverSingle(TargetRedeliveryByTxHashParamsSingle memory targetParams) public payable { //cache wormhole IWormhole wormhole = wormhole(); //validate the redelivery VM (IWormhole.VM memory redeliveryVM, bool valid, string memory reason) = wormhole.parseAndVerifyVM(targetParams.redeliveryVM); if (!valid) { revert InvalidRedeliveryVM(reason); } if (!verifyRelayerVM(redeliveryVM)) { // Redelivery VM has an invalid emitter revert InvalidEmitterInRedeliveryVM(); } RedeliveryByTxHashInstruction memory redeliveryInstruction = decodeRedeliveryByTxHashInstruction(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); } if (!verifyRelayerVM(originalDeliveryVM)) { // Original Delivery VM has a invalid emitter revert InvalidEmitterInOriginalDeliveryVM(redeliveryInstruction.deliveryIndex); } DeliveryInstruction memory instruction; (instruction, valid) = validateRedeliverySingle( redeliveryInstruction, decodeDeliveryInstructionsContainer(originalDeliveryVM.payload).instructions[redeliveryInstruction .multisendIndex] ); if (!valid) { emit Delivery({ recipientContract: fromWormholeFormat(instruction.targetAddress), sourceChain: redeliveryVM.emitterChainId, sequence: redeliveryVM.sequence, deliveryVaaHash: redeliveryVM.hash, status: DeliveryStatus.INVALID_REDELIVERY }); targetParams.relayerRefundAddress.send(msg.value); return; } _executeDelivery( wormhole, instruction, targetParams.sourceEncodedVMs, originalDeliveryVM.hash, targetParams.relayerRefundAddress, originalDeliveryVM.emitterChainId, originalDeliveryVM.sequence ); } function validateRedeliverySingle( RedeliveryByTxHashInstruction memory redeliveryInstruction, DeliveryInstruction memory originalInstruction ) internal view returns (DeliveryInstruction memory deliveryInstruction, bool isValid) { // All the same checks as delivery single, with a couple additional // The same relay provider must be specified when doing a single VAA redeliver. address providerAddress = fromWormholeFormat(redeliveryInstruction.executionParameters.providerDeliveryAddress); if (providerAddress != fromWormholeFormat(originalInstruction.executionParameters.providerDeliveryAddress)) { revert MismatchingRelayProvidersInRedelivery(); } // relayer must have covered the necessary funds if ( msg.value < redeliveryInstruction.newMaximumRefundTarget + redeliveryInstruction.newReceiverValueTarget + wormhole().messageFee() ) { revert InsufficientRelayerFunds(); } uint16 whChainId = chainId(); // msg.sender must be the provider // "Relay provider differed from the specified address"); isValid = msg.sender == providerAddress // redelivery must target this chain // "Redelivery request does not target this chain."); && whChainId == redeliveryInstruction.targetChain // original delivery must target this chain // "Original delivery request did not target this chain."); && whChainId == originalInstruction.targetChain // gasLimit & receiverValue must be at least as large as the initial delivery // "New receiver value is smaller than the original" && originalInstruction.receiverValueTarget <= redeliveryInstruction.newReceiverValueTarget // "New gasLimit is smaller than the original" && originalInstruction.executionParameters.gasLimit <= redeliveryInstruction.executionParameters.gasLimit; // Overwrite compute budget and application budget on the original request and proceed. deliveryInstruction = originalInstruction; deliveryInstruction.maximumRefundTarget = redeliveryInstruction.newMaximumRefundTarget; deliveryInstruction.receiverValueTarget = redeliveryInstruction.newReceiverValueTarget; deliveryInstruction.executionParameters = redeliveryInstruction.executionParameters; } function deliverSingle(TargetDeliveryParametersSingle memory targetParams) public payable { // cache wormhole instance IWormhole wormhole = wormhole(); // validate the deliveryIndex (IWormhole.VM memory deliveryVM, bool valid, string memory reason) = wormhole.parseAndVerifyVM(targetParams.encodedVMs[targetParams.deliveryIndex]); if (!valid) { revert InvalidVaa(targetParams.deliveryIndex); } if (!verifyRelayerVM(deliveryVM)) { revert InvalidEmitter(); } DeliveryInstructionsContainer memory container = decodeDeliveryInstructionsContainer(deliveryVM.payload); //ensure this is a funded delivery, not a failed forward. if (!container.sufficientlyFunded) { revert SendNotSufficientlyFunded(); } // parse the deliveryVM payload into the DeliveryInstructions struct DeliveryInstruction memory deliveryInstruction = container.instructions[targetParams.multisendIndex]; //make sure the specified relayer is the relayer delivering this message if (fromWormholeFormat(deliveryInstruction.executionParameters.providerDeliveryAddress) != msg.sender) { revert UnexpectedRelayer(); } //make sure relayer passed in sufficient funds if ( msg.value < deliveryInstruction.maximumRefundTarget + deliveryInstruction.receiverValueTarget + wormhole.messageFee() ) { revert InsufficientRelayerFunds(); } //make sure this delivery is intended for this chain if (chainId() != deliveryInstruction.targetChain) { revert TargetChainIsNotThisChain(deliveryInstruction.targetChain); } _executeDelivery( wormhole, deliveryInstruction, targetParams.encodedVMs, deliveryVM.hash, targetParams.relayerRefundAddress, deliveryVM.emitterChainId, deliveryVM.sequence ); } function toWormholeFormat(address addr) public pure returns (bytes32 whFormat) { return bytes32(uint256(uint160(addr))); } function fromWormholeFormat(bytes32 whFormatAddress) public pure returns (address addr) { return address(uint160(uint256(whFormatAddress))); } function getDefaultRelayParams() public pure returns (bytes memory relayParams) { return new bytes(0); } function 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 returns (uint256 deliveryQuote) { deliveryQuote = provider.quoteDeliveryOverhead(targetChain) + (gasLimit * provider.quoteGasPrice(targetChain)); } function quoteGasResend(uint16 targetChain, uint32 gasLimit, IRelayProvider provider) public view returns (uint256 redeliveryQuote) { redeliveryQuote = provider.quoteRedeliveryOverhead(targetChain) + (gasLimit * provider.quoteGasPrice(targetChain)); } 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 view returns (uint256 nativeQuote) { (uint16 buffer, uint16 denominator) = provider.getAssetConversionBuffer(targetChain); nativeQuote = assetConversionHelper( targetChain, targetAmount, chainId(), uint256(0) + denominator + buffer, denominator, true, provider ); } //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) ); encoded = abi.encodePacked( encoded, 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 ); //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); newEncoded = abi.encodePacked( newEncoded, calculateTargetDeliveryMaximumRefund(request.targetChain, request.maxTransactionFee, provider), convertApplicationBudgetAmount(request.receiverValue, request.targetChain, provider) ); newEncoded = abi.encodePacked( newEncoded, uint8(1), //version for ExecutionParameters calculateTargetGasDeliveryAmount(request.targetChain, request.maxTransactionFee, provider), provider.getDeliveryAddress(request.targetChain) ); } }