diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 0fc4d3b44..5044789a8 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -19,9 +19,9 @@ env: AVALANCHE_RPC_URL: ${{ secrets.AVALANCHE_RPC_URL }} FANTOM_RPC_URL: ${{ secrets.FANTOM_RPC_URL }} TENDERLY_ACCESS_KEY: ${{ secrets.TENDERLY_ACCESS_KEY }} - TENDERLY_PROJECT_SLUG: 'superform-v1-d5' # your project slug - TENDERLY_ACCOUNT_ID: 'superform' # your username or organization name - FOUNDRY_EXPORTS_OVERWRITE_LATEST: 'true' + TENDERLY_PROJECT_SLUG: "v1" # your project slug + TENDERLY_ACCOUNT_ID: "superform" # your username or organization name + FOUNDRY_EXPORTS_OVERWRITE_LATEST: "true" jobs: build: @@ -85,13 +85,13 @@ jobs: echo "FOUNDRY_FUZZ_SEED=$(echo $(($EPOCHSECONDS / 604800)))" >> $GITHUB_ENV - name: "Run all tests except invariant against the optimized build and produce gas reports" - run: "forge test --gas-report --no-match-path \"test/invariant/**/*.sol\"" + run: 'forge test --gas-report --no-match-path "test/invariant/**/*.sol"' - name: "Add test summary" run: | echo "## Tests result" >> $GITHUB_STEP_SUMMARY echo "✅ Passed" >> $GITHUB_STEP_SUMMARY - + coverage: runs-on: SuperformCore2 steps: @@ -127,9 +127,9 @@ jobs: submodules: "recursive" - name: "Install Foundry" uses: "foundry-rs/foundry-toolchain@v1" - - name: 'Install Tenderly CLI' + - name: "Install Tenderly CLI" run: curl https://raw.githubusercontent.com/Tenderly/tenderly-cli/master/scripts/install-linux.sh | sudo sh - - name: 'Deploy to Tenderly Devnets' + - name: "Deploy to Tenderly Devnets" run: ./script/utils/run_script_tenderly.sh shell: bash - name: "Add devnet deployment summary" diff --git a/script/utils/run_script_tenderly.sh b/script/utils/run_script_tenderly.sh index 1ab854a65..ad7de9ea6 100755 --- a/script/utils/run_script_tenderly.sh +++ b/script/utils/run_script_tenderly.sh @@ -3,17 +3,13 @@ # Read the RPC URL source .env -#BSC_DEVNET=$(tenderly devnet spawn-rpc --project $TENDERLY_PROJECT_SLUG --template bscdevnet --account $TENDERLY_ACCOUNT_ID --access_key $TENDERLY_ACCESS_KEY --return-url) -#POLYGON_DEVNET=$(tenderly devnet spawn-rpc --project $TENDERLY_PROJECT_SLUG --template polygondevnet --account $TENDERLY_ACCOUNT_ID --access_key $TENDERLY_ACCESS_KEY --return-url) -#AVAX_DEVNET=$(tenderly devnet spawn-rpc --project $TENDERLY_PROJECT_SLUG --template avaxdevnet --account $TENDERLY_ACCOUNT_ID --access_key $TENDERLY_ACCESS_KEY --return-url) -ETHEREUM_DEVNET=$(tenderly devnet spawn-rpc --project $TENDERLY_PROJECT_SLUG --template ethereumdevnet --account $TENDERLY_ACCOUNT_ID --access_key $TENDERLY_ACCESS_KEY --return-url) -OPTIMISM_DEVNET=$(tenderly devnet spawn-rpc --project $TENDERLY_PROJECT_SLUG --template optimismdevnet --account $TENDERLY_ACCOUNT_ID --access_key $TENDERLY_ACCESS_KEY --return-url) -ARBITRUM_DEVNET=$(tenderly devnet spawn-rpc --project $TENDERLY_PROJECT_SLUG --template arbitrumdevnet --account $TENDERLY_ACCOUNT_ID --access_key $TENDERLY_ACCESS_KEY --return-url) +ETHEREUM_DEVNET=$(tenderly devnet spawn-rpc --project $TENDERLY_PROJECT_SLUG --template ethereum-devnet --account $TENDERLY_ACCOUNT_ID --access_key $TENDERLY_ACCESS_KEY --return-url) +OPTIMISM_DEVNET=$(tenderly devnet spawn-rpc --project $TENDERLY_PROJECT_SLUG --template optimism-devnet --account $TENDERLY_ACCOUNT_ID --access_key $TENDERLY_ACCESS_KEY --return-url) +ARBITRUM_DEVNET=$(tenderly devnet spawn-rpc --project $TENDERLY_PROJECT_SLUG --template arbitrum-devnet --account $TENDERLY_ACCOUNT_ID --access_key $TENDERLY_ACCESS_KEY --return-url) # Run the script echo Running Stage 1: ... - FOUNDRY_PROFILE=default forge script script/Tenderly.Deploy.s.sol:TenderlyDeploy --sig "deployStage1(uint256,uint256)" 0 1337 --rpc-url $ETHEREUM_DEVNET --broadcast --unlocked --sender 0x0000000000000000000000000000000000000000 wait @@ -28,7 +24,6 @@ wait echo Running Stage 2: ... - FOUNDRY_PROFILE=default forge script script/Tenderly.Deploy.s.sol:TenderlyDeploy --sig "deployStage2(uint256)" 0 --rpc-url $ETHEREUM_DEVNET --broadcast --unlocked --sender 0x0000000000000000000000000000000000000000 wait @@ -52,4 +47,3 @@ FOUNDRY_PROFILE=default forge script script/Tenderly.Deploy.s.sol:TenderlyDeploy wait FOUNDRY_PROFILE=default forge script script/Tenderly.Deploy.s.sol:TenderlyDeploy --sig "deployStage3(uint256)" 2 --rpc-url $ARBITRUM_DEVNET --broadcast --unlocked --sender 0x0000000000000000000000000000000000000000 - diff --git a/src/interfaces/IPaymentHelper.sol b/src/interfaces/IPaymentHelper.sol index 4423e9c53..732e5accd 100644 --- a/src/interfaces/IPaymentHelper.sol +++ b/src/interfaces/IPaymentHelper.sol @@ -73,22 +73,6 @@ interface IPaymentHelper { /// @return extraData the amb specific override information function getRegisterTransmuterAMBData() external view returns (bytes memory extraData); - /// @dev returns the gas fees estimation in native tokens if we send message through a combination of AMBs - /// @param ambIds_ is the identifier of different AMBs - /// @param dstChainId_ is the identifier of the destination chain - /// @param message_ is the cross-chain message - /// @param extraData_ is any amb-specific information - /// @return ambFees is the native_tokens to be sent along the transaction for all the ambIds_ included - function estimateAMBFees( - uint8[] memory ambIds_, - uint64 dstChainId_, - bytes memory message_, - bytes[] memory extraData_ - ) - external - view - returns (uint256 ambFees, uint256[] memory); - /// @dev estimates the gas fees for multiple destination and multi vault operation /// @param req_ is the request object containing all necessary data for the actual operation on SuperRouter /// @param isDeposit_ indicated if the datatype will be used for a deposit @@ -177,6 +161,55 @@ interface IPaymentHelper { view returns (uint256 liqAmount, uint256 srcAmount, uint256 totalAmount); + /// @dev returns the gas fees estimation in native tokens if we send message through a combination of AMBs + /// @param ambIds_ is the identifier of different AMBs + /// @param dstChainId_ is the identifier of the destination chain + /// @param message_ is the cross-chain message + /// @param extraData_ is any amb-specific information + /// @return ambFees is the native_tokens to be sent along the transaction for all the ambIds_ included + function estimateAMBFees( + uint8[] memory ambIds_, + uint64 dstChainId_, + bytes memory message_, + bytes[] memory extraData_ + ) + external + view + returns (uint256 ambFees, uint256[] memory); + + /// @dev helps estimate the acknowledgement costs for amb processing + /// @param payloadId_ is the payload identifier + /// @return totalFees is the total fees to be paid in native tokens + function estimateAckCost(uint256 payloadId_) external view returns (uint256 totalFees); + + /// @dev helps estimate the acknowledgement costs for amb processing without relying on payloadId (using max values) + /// @param multi is the flag indicating if the payload is multi or single + /// @param ackAmbIds is the list of ambIds to be used for acknowledgement + /// @param srcChainId is the source chain identifier + /// @return totalFees is the total fees to be paid in native tokens + function estimateAckCostDefault( + bool multi, + uint8[] memory ackAmbIds, + uint64 srcChainId + ) + external + view + returns (uint256 totalFees); + + /// @dev helps estimate the acknowledgement costs for amb processing without relying on payloadId (using max values) + /// with source native amounts + /// @param multi is the flag indicating if the payload is multi or single + /// @param ackAmbIds is the list of ambIds to be used for acknowledgement + /// @param srcChainId is the source chain identifier + /// @return totalFees is the total fees to be paid in native tokens + function estimateAckCostDefaultNativeSource( + bool multi, + uint8[] memory ackAmbIds, + uint64 srcChainId + ) + external + view + returns (uint256 totalFees); ////////////////////////////////////////////////////////////// // EXTERNAL WRITE FUNCTIONS // ////////////////////////////////////////////////////////////// diff --git a/src/payments/PaymentHelper.sol b/src/payments/PaymentHelper.sol index 3cd64e58b..5e15f6e1e 100644 --- a/src/payments/PaymentHelper.sol +++ b/src/payments/PaymentHelper.sol @@ -106,14 +106,14 @@ contract PaymentHelper is IPaymentHelper { ////////////////////////////////////////////////////////////// modifier onlyProtocolAdmin() { - if (!ISuperRBAC(superRegistry.getAddress(keccak256("SUPER_RBAC"))).hasProtocolAdminRole(msg.sender)) { + if (!ISuperRBAC(_getAddress(keccak256("SUPER_RBAC"))).hasProtocolAdminRole(msg.sender)) { revert Error.NOT_PROTOCOL_ADMIN(); } _; } modifier onlyEmergencyAdmin() { - if (!ISuperRBAC(superRegistry.getAddress(keccak256("SUPER_RBAC"))).hasEmergencyAdminRole(msg.sender)) { + if (!ISuperRBAC(_getAddress(keccak256("SUPER_RBAC"))).hasEmergencyAdminRole(msg.sender)) { revert Error.NOT_EMERGENCY_ADMIN(); } _; @@ -176,7 +176,7 @@ contract PaymentHelper is IPaymentHelper { LocalEstimateVars memory v; v.len = req_.dstChainIds.length; - ISuperformFactory factory = ISuperformFactory(superRegistry.getAddress(keccak256("SUPERFORM_FACTORY"))); + ISuperformFactory factory = ISuperformFactory(_getAddress(keccak256("SUPERFORM_FACTORY"))); for (uint256 i; i < v.len; ++i) { bool xChain = req_.dstChainIds[i] != CHAIN_ID; @@ -225,19 +225,7 @@ contract PaymentHelper is IPaymentHelper { /// @dev step 7: estimate execution costs in destination including sending acknowledgement to source /// @dev ensure that acknowledgement costs from dst to src are not double counted - bool noRetain4626; - for (uint256 j; j < v.superformIdsLen; ++j) { - if (!req_.superformsData[i].retain4626s[j]) { - noRetain4626 = true; - break; - } - } - if (noRetain4626 && xChain) { - v.totalDstGas += _estimateDstExecutionCost(isDeposit_, false, req_.dstChainIds[i], v.superformIdsLen); - } else { - v.totalDstGas += - xChain ? _estimateDstExecutionCost(isDeposit_, true, req_.dstChainIds[i], v.superformIdsLen) : 0; - } + v.totalDstGas += xChain ? _estimateDstExecutionCost(isDeposit_, req_.dstChainIds[i], v.superformIdsLen) : 0; /// @dev step 8: convert all dst gas estimates to src chain estimate (withdraw / deposit) dstAmount += _convertToNativeFee(req_.dstChainIds[i], v.totalDstGas); @@ -257,7 +245,7 @@ contract PaymentHelper is IPaymentHelper { returns (uint256 liqAmount, uint256 srcAmount, uint256 dstAmount, uint256 totalAmount) { uint256 len = req_.dstChainIds.length; - ISuperformFactory factory = ISuperformFactory(superRegistry.getAddress(keccak256("SUPERFORM_FACTORY"))); + ISuperformFactory factory = ISuperformFactory(_getAddress(keccak256("SUPERFORM_FACTORY"))); for (uint256 i; i < len; ++i) { bool xChain = req_.dstChainIds[i] != CHAIN_ID; @@ -301,9 +289,7 @@ contract PaymentHelper is IPaymentHelper { } /// @dev step 7: estimate execution costs in destination including sending acknowledgement to source - totalDstGas += xChain - ? _estimateDstExecutionCost(isDeposit_, req_.superformsData[i].retain4626, req_.dstChainIds[i], 1) - : 0; + totalDstGas += xChain ? _estimateDstExecutionCost(isDeposit_, req_.dstChainIds[i], 1) : 0; /// @dev step 8: convert all dst gas estimates to src chain estimate dstAmount += _convertToNativeFee(req_.dstChainIds[i], totalDstGas); @@ -325,7 +311,7 @@ contract PaymentHelper is IPaymentHelper { uint256 totalDstGas; uint256 superformIdsLen = req_.superformsData.superformIds.length; - ISuperformFactory factory = ISuperformFactory(superRegistry.getAddress(keccak256("SUPERFORM_FACTORY"))); + ISuperformFactory factory = ISuperformFactory(_getAddress(keccak256("SUPERFORM_FACTORY"))); /// @dev step 1: estimate AMB costs uint256 ambFees = @@ -366,18 +352,7 @@ contract PaymentHelper is IPaymentHelper { /// @dev step 7: estimate execution costs in destination including sending acknowledgement to source /// @dev ensure that acknowledgement costs from dst to src are not double counted - bool noRetain4626; - for (uint256 i; i < superformIdsLen; ++i) { - if (!req_.superformsData.retain4626s[i]) { - noRetain4626 = true; - break; - } - } - if (noRetain4626) { - totalDstGas += _estimateDstExecutionCost(isDeposit_, false, req_.dstChainId, superformIdsLen); - } else { - totalDstGas += _estimateDstExecutionCost(isDeposit_, true, req_.dstChainId, superformIdsLen); - } + totalDstGas += _estimateDstExecutionCost(isDeposit_, req_.dstChainId, superformIdsLen); /// @dev step 8: convert all destination gas estimates to source chain estimate dstAmount += _convertToNativeFee(req_.dstChainId, totalDstGas); @@ -396,7 +371,7 @@ contract PaymentHelper is IPaymentHelper { returns (uint256 liqAmount, uint256 srcAmount, uint256 dstAmount, uint256 totalAmount) { uint256 totalDstGas; - ISuperformFactory factory = ISuperformFactory(superRegistry.getAddress(keccak256("SUPERFORM_FACTORY"))); + ISuperformFactory factory = ISuperformFactory(_getAddress(keccak256("SUPERFORM_FACTORY"))); /// @dev step 1: estimate AMB costs uint256 ambFees = @@ -431,7 +406,7 @@ contract PaymentHelper is IPaymentHelper { } /// @dev step 7: estimate execution costs in destination including sending acknowledgement to source - totalDstGas += _estimateDstExecutionCost(isDeposit_, req_.superformData.retain4626, req_.dstChainId, 1); + totalDstGas += _estimateDstExecutionCost(isDeposit_, req_.dstChainId, 1); /// @dev step 8: convert all destination gas estimates to source chain estimate dstAmount += _convertToNativeFee(req_.dstChainId, totalDstGas); @@ -449,7 +424,7 @@ contract PaymentHelper is IPaymentHelper { override returns (uint256 liqAmount, uint256 srcAmount, uint256 totalAmount) { - ISuperformFactory factory = ISuperformFactory(superRegistry.getAddress(keccak256("SUPERFORM_FACTORY"))); + ISuperformFactory factory = ISuperformFactory(_getAddress(keccak256("SUPERFORM_FACTORY"))); if (!isDeposit_) { /// @dev only if timelock form withdrawal is involved @@ -480,7 +455,7 @@ contract PaymentHelper is IPaymentHelper { override returns (uint256 liqAmount, uint256 srcAmount, uint256 totalAmount) { - ISuperformFactory factory = ISuperformFactory(superRegistry.getAddress(keccak256("SUPERFORM_FACTORY"))); + ISuperformFactory factory = ISuperformFactory(_getAddress(keccak256("SUPERFORM_FACTORY"))); if (!isDeposit_) { uint256 len = req_.superformData.superformIds.length; @@ -513,6 +488,7 @@ contract PaymentHelper is IPaymentHelper { ) public view + override returns (uint256 totalFees, uint256[] memory) { uint256 len = ambIds_.length; @@ -532,11 +508,10 @@ contract PaymentHelper is IPaymentHelper { return (totalFees, fees); } - /// @dev helps estimate the acknowledgement costs for amb processing - function estimateAckCost(uint256 payloadId_) external view returns (uint256 totalFees) { + /// @inheritdoc IPaymentHelper + function estimateAckCost(uint256 payloadId_) external view override returns (uint256 totalFees) { EstimateAckCostVars memory v; - IBaseStateRegistry coreStateRegistry = - IBaseStateRegistry(superRegistry.getAddress(keccak256("CORE_STATE_REGISTRY"))); + IBaseStateRegistry coreStateRegistry = IBaseStateRegistry(_getAddress(keccak256("CORE_STATE_REGISTRY"))); v.currPayloadId = coreStateRegistry.payloadsCount(); if (payloadId_ > v.currPayloadId) revert Error.INVALID_PAYLOAD_ID(); @@ -564,6 +539,47 @@ contract PaymentHelper is IPaymentHelper { return _estimateAMBFees(v.ackAmbIds, v.srcChainId, v.message); } + /// @inheritdoc IPaymentHelper + function estimateAckCostDefault( + bool multi, + uint8[] memory ackAmbIds, + uint64 srcChainId + ) + public + view + override + returns (uint256 totalFees) + { + bytes memory payloadBody; + if (multi) { + uint256 vaultLimitPerDst = superRegistry.getVaultLimitPerDestination(srcChainId); + uint256[] memory maxUints = new uint256[](vaultLimitPerDst); + + for (uint256 i; i < vaultLimitPerDst; ++i) { + maxUints[i] = type(uint256).max; + } + payloadBody = abi.encode(ReturnMultiData(type(uint256).max, maxUints, maxUints)); + } else { + payloadBody = abi.encode(ReturnSingleData(type(uint256).max, type(uint256).max, type(uint256).max)); + } + + return _estimateAMBFees(ackAmbIds, srcChainId, abi.encode(AMBMessage(type(uint256).max, payloadBody))); + } + + /// @inheritdoc IPaymentHelper + function estimateAckCostDefaultNativeSource( + bool multi, + uint8[] memory ackAmbIds, + uint64 srcChainId + ) + external + view + override + returns (uint256) + { + return _convertToNativeFee(srcChainId, estimateAckCostDefault(multi, ackAmbIds, srcChainId)); + } + ////////////////////////////////////////////////////////////// // EXTERNAL WRITE FUNCTIONS // ////////////////////////////////////////////////////////////// @@ -852,7 +868,6 @@ contract PaymentHelper is IPaymentHelper { /// @dev assumes that withdrawals optimisically succeed function _estimateDstExecutionCost( bool isDeposit_, - bool retain4626_, uint64 dstChainId_, uint256 vaultsCount_ ) @@ -862,11 +877,6 @@ contract PaymentHelper is IPaymentHelper { { uint256 executionGasPerVault = isDeposit_ ? depositGasUsed[dstChainId_] : withdrawGasUsed[dstChainId_]; gasUsed = executionGasPerVault * vaultsCount_; - - /// @dev add ackGasCost only if it's a deposit and retain4626 is false - if (isDeposit_ && !retain4626_) { - gasUsed += ackGasCost[dstChainId_]; - } } /// @dev helps estimate the src chain processing fee @@ -953,7 +963,7 @@ contract PaymentHelper is IPaymentHelper { /// @dev helps generate the new payload id /// @dev next payload id = current payload id + 1 function _getNextPayloadId() internal view returns (uint256 nextPayloadId) { - nextPayloadId = ReadOnlyBaseRegistry(superRegistry.getAddress(keccak256("CORE_STATE_REGISTRY"))).payloadsCount(); + nextPayloadId = ReadOnlyBaseRegistry(_getAddress(keccak256("CORE_STATE_REGISTRY"))).payloadsCount(); ++nextPayloadId; } @@ -994,4 +1004,9 @@ contract PaymentHelper is IPaymentHelper { return nativePrice[chainId_]; } + + /// @dev returns the address from super registry + function _getAddress(bytes32 id_) internal view returns (address) { + return superRegistry.getAddress(id_); + } } diff --git a/test/unit/crosschain-data/extensions/CoreStateRegistry.t.sol b/test/unit/crosschain-data/extensions/CoreStateRegistry.t.sol index dca2961fd..39af9451f 100644 --- a/test/unit/crosschain-data/extensions/CoreStateRegistry.t.sol +++ b/test/unit/crosschain-data/extensions/CoreStateRegistry.t.sol @@ -673,6 +673,56 @@ contract CoreStateRegistryTest is ProtocolActions { vm.clearMockedCalls(); } + function test_ackGasCost_single_paymentHelperComparison() public { + uint8[] memory ambIds_ = new uint8[](2); + ambIds_[0] = 1; + ambIds_[1] = 2; + + _successfulSingleDeposit(ambIds_); + + vm.selectFork(FORKS[AVAX]); + + uint256 defaultEstimate = + PaymentHelper(getContract(AVAX, "PaymentHelper")).estimateAckCostDefault(false, ambIds_, ETH); + + uint256 realEstimate = PaymentHelper(getContract(AVAX, "PaymentHelper")).estimateAckCost(1); + + console.log("defaultEstimate: %s", defaultEstimate); + console.log("realEstimate: %s", realEstimate); + + assertEq(realEstimate, defaultEstimate); + + uint256 defaultEstimateNativeSrc = + PaymentHelper(getContract(AVAX, "PaymentHelper")).estimateAckCostDefaultNativeSource(false, ambIds_, ETH); + + console.log("defaultEstimateNativeSrc: %s", defaultEstimateNativeSrc); + } + + function test_ackGasCost_multi_paymentHelperComparison() public { + uint8[] memory ambIds_ = new uint8[](2); + ambIds_[0] = 1; + ambIds_[1] = 2; + + _successfulMultiDeposit(ambIds_); + + vm.selectFork(FORKS[AVAX]); + + uint256 defaultEstimate = + PaymentHelper(getContract(AVAX, "PaymentHelper")).estimateAckCostDefault(true, ambIds_, ETH); + + uint256 realEstimate = PaymentHelper(getContract(AVAX, "PaymentHelper")).estimateAckCost(1); + + console.log("defaultEstimate: %s", defaultEstimate); + console.log("realEstimate: %s", realEstimate); + + assertLe(realEstimate, defaultEstimate); + + uint256 defaultEstimateNativeSrc = + PaymentHelper(getContract(AVAX, "PaymentHelper")).estimateAckCostDefaultNativeSource(true, ambIds_, ETH); + + console.log("defaultEstimateNativeSrc: %s", defaultEstimateNativeSrc); + } + /*/////////////////////////////////////////////////////////////// INTERNAL FUNCTIONS //////////////////////////////////////////////////////////////*/