diff --git a/ethereum/contracts/relayer/deliveryProvider/DeliveryProvider.sol b/ethereum/contracts/relayer/deliveryProvider/DeliveryProvider.sol index 83e16c36e..54b04f453 100644 --- a/ethereum/contracts/relayer/deliveryProvider/DeliveryProvider.sol +++ b/ethereum/contracts/relayer/deliveryProvider/DeliveryProvider.sol @@ -8,6 +8,7 @@ import "../../interfaces/relayer/IDeliveryProviderTyped.sol"; import "../../interfaces/relayer/TypedUnits.sol"; import "../../libraries/relayer/ExecutionParameters.sol"; import {IWormhole} from "../../interfaces/IWormhole.sol"; +import "forge-std/console.sol"; contract DeliveryProvider is DeliveryProviderGovernance, IDeliveryProvider { using WeiLib for Wei; @@ -29,17 +30,20 @@ contract DeliveryProvider is DeliveryProviderGovernance, IDeliveryProvider { returns (LocalNative nativePriceQuote, GasPrice targetChainRefundPerUnitGasUnused) { (uint16 buffer, uint16 denominator) = assetConversionBuffer(targetChain); - targetChainRefundPerUnitGasUnused = GasPrice.wrap(gasPrice(targetChain).unwrap() * (denominator - buffer) / denominator); + targetChainRefundPerUnitGasUnused = GasPrice.wrap(gasPrice(targetChain).unwrap() * (denominator) / (uint256(denominator) + buffer)); TargetNative targetCostIfNoGasLimitUsed = gasLimit.toWei(targetChainRefundPerUnitGasUnused).asTargetNative(); TargetNative targetCostIfFullGasLimitUsed = gasLimit.toWei(gasPrice(targetChain)).asTargetNative(); LocalNative costIfNoGasLimitUsed = quoteAssetCost(targetChain, targetCostIfNoGasLimitUsed); - LocalNative costIfFullGasLimitUsed = gasLimit.toWei(quoteGasPrice(targetChain)).asLocalNative(); + LocalNative costIfFullGasLimitUsed = quoteGasCost(targetChain, gasLimit); LocalNative receiverValueCost = quoteAssetCost(targetChain, receiverValue); - nativePriceQuote = quoteDeliveryOverhead(targetChain) + - costIfFullGasLimitUsed.asNative().max(costIfNoGasLimitUsed.asNative()).asLocalNative() + receiverValueCost; + nativePriceQuote = quoteDeliveryOverhead(targetChain) + costIfFullGasLimitUsed + receiverValueCost; + console.log(costIfNoGasLimitUsed.unwrap()); + console.log(costIfFullGasLimitUsed.unwrap()); + require(costIfNoGasLimitUsed.unwrap() <= costIfFullGasLimitUsed.unwrap(), "target chain refund per gas unused is too high"); + require(targetCostIfNoGasLimitUsed.unwrap() <= targetCostIfFullGasLimitUsed.unwrap(), "target chain refund per gas unused is too high"); require( - receiverValue.asNative() + targetCostIfNoGasLimitUsed.asNative().max(targetCostIfFullGasLimitUsed.asNative()) <= maximumBudget(targetChain).asNative(), + receiverValue.asNative() + targetCostIfFullGasLimitUsed.asNative() <= maximumBudget(targetChain).asNative(), "Exceeds maximum budget" ); } @@ -82,12 +86,10 @@ contract DeliveryProvider is DeliveryProviderGovernance, IDeliveryProvider { return sourceChainAmount.asNative().convertAsset( nativeCurrencyPrice(sourceChain), nativeCurrencyPrice(targetChain), - (buffer), + (bufferDenominator), (uint32(buffer) + bufferDenominator), - false - ) - // round down - .asTargetNative(); + false // round down + ).asTargetNative(); } //Returns the address on this chain that rewards should be sent to @@ -116,27 +118,36 @@ contract DeliveryProvider is DeliveryProviderGovernance, IDeliveryProvider { //Returns the delivery overhead fee required to deliver a message to the target chain, denominated in this chain's wei. function quoteDeliveryOverhead(uint16 targetChain) public view returns (LocalNative nativePriceQuote) { - Gas overhead = deliverGasOverhead(targetChain); - Wei targetFees = overhead.toWei(gasPrice(targetChain)); - Wei result = assetConversion(targetChain, targetFees, chainId()); - require(result.unwrap() <= type(uint128).max, "Overflow"); - return result.asLocalNative(); + nativePriceQuote = quoteGasCost(targetChain, deliverGasOverhead(targetChain)); + require(nativePriceQuote.unwrap() <= type(uint128).max, "Overflow"); } - //Returns the price of purchasing 1 unit of gas on the target chain, denominated in this chain's wei. - function quoteGasPrice(uint16 targetChain) public view returns (GasPrice) { - Wei gasPriceInSourceChainCurrency = - assetConversion(targetChain, gasPrice(targetChain).priceAsWei(), chainId()); - require(gasPriceInSourceChainCurrency.unwrap() <= type(uint88).max, "Overflow"); - return GasPrice.wrap(uint88(gasPriceInSourceChainCurrency.unwrap())); + //Returns the price of purchasing gasAmount units of gas on the target chain, denominated in this chain's wei. + function quoteGasCost(uint16 targetChain, Gas gasAmount) public view returns (LocalNative totalCost) { + Wei gasCostInSourceChainCurrency = + assetConversion(targetChain, gasAmount.toWei(gasPrice(targetChain)), chainId()); + totalCost = LocalNative.wrap(gasCostInSourceChainCurrency.unwrap()); } + function quoteGasPrice(uint16 targetChain) public view returns (GasPrice price) { + price = GasPrice.wrap(quoteGasCost(targetChain, Gas.wrap(1)).unwrap()); + require(price.unwrap() <= type(uint88).max, "Overflow"); + } + + error PriceIsZero(uint16 chain); + // relevant for chains that have dynamic execution pricing (e.g. Ethereum) function assetConversion( uint16 sourceChain, Wei sourceAmount, uint16 targetChain ) internal view returns (Wei targetAmount) { + if(nativeCurrencyPrice(sourceChain).unwrap() == 0) { + revert PriceIsZero(sourceChain); + } + if(nativeCurrencyPrice(targetChain).unwrap() == 0) { + revert PriceIsZero(targetChain); + } return sourceAmount.convertAsset( nativeCurrencyPrice(sourceChain), nativeCurrencyPrice(targetChain), @@ -152,11 +163,17 @@ contract DeliveryProvider is DeliveryProviderGovernance, IDeliveryProvider { TargetNative targetChainAmount ) internal view returns (LocalNative currentChainAmount) { (uint16 buffer, uint16 bufferDenominator) = assetConversionBuffer(targetChain); + if(nativeCurrencyPrice(chainId()).unwrap() == 0) { + revert PriceIsZero(chainId()); + } + if(nativeCurrencyPrice(targetChain).unwrap() == 0) { + revert PriceIsZero(targetChain); + } return targetChainAmount.asNative().convertAsset( - nativeCurrencyPrice(chainId()), nativeCurrencyPrice(targetChain), + nativeCurrencyPrice(chainId()), (uint32(buffer) + bufferDenominator), - (buffer), + (bufferDenominator), // round up true ).asLocalNative(); diff --git a/ethereum/forge-test/relayer/RelayProvider.t.sol b/ethereum/forge-test/relayer/RelayProvider.t.sol index 6926cecb4..ebe18aca9 100644 --- a/ethereum/forge-test/relayer/RelayProvider.t.sol +++ b/ethereum/forge-test/relayer/RelayProvider.t.sol @@ -120,53 +120,52 @@ contract TestDeliveryProvider is Test { deliveryProvider.updatePrice(updateChainId, updateGasPrice, updateNativeCurrencyPrice); } - /* - TODO: Uncomment these tests once revert messages are back in + function testCannotGetPriceBeforeUpdateSrcPrice( uint16 dstChainId, - uint128 dstGasPrice, - uint128 dstNativeCurrencyPrice + uint64 dstGasPrice, + WeiPrice dstNativeCurrencyPrice ) public { vm.assume(dstChainId > 0); vm.assume(dstChainId != TEST_ORACLE_CHAIN_ID); vm.assume(dstGasPrice > 0); - vm.assume(dstNativeCurrencyPrice > 0); + vm.assume(dstNativeCurrencyPrice.unwrap() > 0); initializeDeliveryProvider(); // update the price with reasonable values - deliveryProvider.updatePrice(dstChainId, dstGasPrice, dstNativeCurrencyPrice); + deliveryProvider.updatePrice(dstChainId, GasPrice.wrap(dstGasPrice), dstNativeCurrencyPrice); // you shall not pass - vm.expectRevert("srcNativeCurrencyPrice == 0"); + vm.expectRevert(abi.encodeWithSignature("PriceIsZero(uint16)", TEST_ORACLE_CHAIN_ID)); deliveryProvider.quoteDeliveryOverhead(dstChainId); } function testCannotGetPriceBeforeUpdateDstPrice( uint16 dstChainId, - uint128 srcGasPrice, - uint128 srcNativeCurrencyPrice + uint64 srcGasPrice, + WeiPrice srcNativeCurrencyPrice ) public { vm.assume(dstChainId > 0); vm.assume(dstChainId != TEST_ORACLE_CHAIN_ID); vm.assume(srcGasPrice > 0); - vm.assume(srcNativeCurrencyPrice > 0); + vm.assume(srcNativeCurrencyPrice.unwrap() > 0); initializeDeliveryProvider(); // update the price with reasonable values //vm.prank(deliveryProvider.owner()); - deliveryProvider.updatePrice(TEST_ORACLE_CHAIN_ID, srcGasPrice, srcNativeCurrencyPrice); + deliveryProvider.updatePrice(TEST_ORACLE_CHAIN_ID, GasPrice.wrap(srcGasPrice), srcNativeCurrencyPrice); // you shall not pass - vm.expectRevert("dstNativeCurrencyPrice == 0"); + vm.expectRevert(abi.encodeWithSignature("PriceIsZero(uint16)", dstChainId)); deliveryProvider.quoteDeliveryOverhead(dstChainId); } - */ + function testUpdatePrice( uint16 dstChainId, @@ -268,4 +267,111 @@ contract TestDeliveryProvider is Test { assertTrue(newAddress == updated); } + + function testQuoteDeliveryOverhead( + uint16 dstChainId, + uint64 dstGasPrice, + uint64 dstNativeCurrencyPrice, + uint64 srcGasPrice, + uint64 srcNativeCurrencyPrice, + uint32 gasOverhead + ) public { + initializeDeliveryProvider(); + + vm.assume(dstChainId > 0); + vm.assume(dstChainId != TEST_ORACLE_CHAIN_ID); // wormhole.chainId() + vm.assume(dstGasPrice > 0); + vm.assume(dstNativeCurrencyPrice > 0); + vm.assume(srcGasPrice > 0); + vm.assume(srcNativeCurrencyPrice > 0); + vm.assume(dstGasPrice >= dstNativeCurrencyPrice / srcNativeCurrencyPrice); + vm.assume(dstGasPrice * uint256(dstNativeCurrencyPrice) / srcNativeCurrencyPrice < 2 ** 72); + + vm.assume(gasOverhead < uint256(2)**31); + + // update the prices with reasonable values + deliveryProvider.updatePrice( + dstChainId, GasPrice.wrap(dstGasPrice), WeiPrice.wrap(dstNativeCurrencyPrice) + ); + deliveryProvider.updatePrice( + TEST_ORACLE_CHAIN_ID, GasPrice.wrap(srcGasPrice), WeiPrice.wrap(srcNativeCurrencyPrice) + ); + + deliveryProvider.updateAssetConversionBuffer(dstChainId, 5, 100); + + deliveryProvider.updateDeliverGasOverhead(dstChainId, Gas.wrap(gasOverhead)); + + deliveryProvider.updateMaximumBudget(dstChainId, Wei.wrap(uint256(2)**191)); + + // verify price + uint256 expectedOverhead = ( + uint256(dstNativeCurrencyPrice) * (uint256(dstGasPrice) * gasOverhead) + (srcNativeCurrencyPrice - 1) + ) / srcNativeCurrencyPrice; + + console.log(expectedOverhead); + + LocalNative deliveryOverhead = deliveryProvider.quoteDeliveryOverhead(dstChainId); + + require(expectedOverhead == LocalNative.unwrap(deliveryOverhead), "deliveryProvider overhead quote is not what is expected"); + } + + function testQuoteDeliveryPrice( + uint16 dstChainId, + uint64 dstGasPrice, + uint64 dstNativeCurrencyPrice, + uint64 srcGasPrice, + uint64 srcNativeCurrencyPrice, + uint32 gasLimit, + uint32 gasOverhead, + uint64 receiverValue + ) public { + initializeDeliveryProvider(); + + vm.assume(dstChainId > 0); + vm.assume(dstChainId != TEST_ORACLE_CHAIN_ID); // wormhole.chainId() + vm.assume(dstGasPrice > 0); + vm.assume(dstNativeCurrencyPrice > 0); + vm.assume(srcGasPrice > 0); + vm.assume(srcNativeCurrencyPrice > 0); + vm.assume(dstGasPrice >= dstNativeCurrencyPrice / srcNativeCurrencyPrice); + vm.assume(dstGasPrice * uint256(dstNativeCurrencyPrice) / srcNativeCurrencyPrice < 2 ** 72); + + vm.assume(gasLimit < uint256(2)**31); + vm.assume(gasOverhead < uint256(2)**31); + + // update the prices with reasonable values + deliveryProvider.updatePrice( + dstChainId, GasPrice.wrap(dstGasPrice), WeiPrice.wrap(dstNativeCurrencyPrice) + ); + deliveryProvider.updatePrice( + TEST_ORACLE_CHAIN_ID, GasPrice.wrap(srcGasPrice), WeiPrice.wrap(srcNativeCurrencyPrice) + ); + + deliveryProvider.updateAssetConversionBuffer(dstChainId, 5, 100); + + deliveryProvider.updateDeliverGasOverhead(dstChainId, Gas.wrap(gasOverhead)); + + deliveryProvider.updateMaximumBudget(dstChainId, Wei.wrap(uint256(2)**191)); + + // verify price + uint256 expectedGasCost = ( + uint256(dstNativeCurrencyPrice) * (uint256(dstGasPrice) * (gasLimit)) + (srcNativeCurrencyPrice - 1) + ) / srcNativeCurrencyPrice; + + uint256 expectedOverheadCost = ( + uint256(dstNativeCurrencyPrice) * (uint256(dstGasPrice) * ( gasOverhead)) + (srcNativeCurrencyPrice - 1) + ) / srcNativeCurrencyPrice; + + uint256 expectedReceiverValueCost = ( + uint256(dstNativeCurrencyPrice) * (receiverValue) * 105 + (srcNativeCurrencyPrice * uint256(100) - 1) + ) / srcNativeCurrencyPrice / 100; + + (LocalNative nativePriceQuote,) = deliveryProvider.quoteEvmDeliveryPrice(dstChainId, Gas.wrap(gasLimit), TargetNative.wrap(receiverValue)); + + require(expectedGasCost == LocalNative.unwrap(deliveryProvider.quoteGasCost(dstChainId, Gas.wrap(gasLimit))), "Gas cost is not what is expected"); + require(expectedOverheadCost == LocalNative.unwrap(deliveryProvider.quoteGasCost(dstChainId, Gas.wrap(gasOverhead))), "Overhead cost is not what is expected"); + + require(expectedGasCost + expectedOverheadCost + expectedReceiverValueCost == LocalNative.unwrap(nativePriceQuote), "deliveryProvider price quote is not what is expected"); + + } }