diff --git a/contracts/src/v0.8/ccip/offRamp/EVM2EVMOffRamp.sol b/contracts/src/v0.8/ccip/offRamp/EVM2EVMOffRamp.sol index ca696e8f91..f39cefd8b9 100644 --- a/contracts/src/v0.8/ccip/offRamp/EVM2EVMOffRamp.sol +++ b/contracts/src/v0.8/ccip/offRamp/EVM2EVMOffRamp.sol @@ -45,7 +45,9 @@ contract EVM2EVMOffRamp is IAny2EVMOffRamp, AggregateRateLimiter, ITypeAndVersio error UnsupportedNumberOfTokens(uint64 sequenceNumber); error ManualExecutionNotYetEnabled(); error ManualExecutionGasLimitMismatch(); + error DestinationGasAmountCountMismatch(bytes32 messageId, uint64 sequenceNumber); error InvalidManualExecutionGasLimit(uint256 index, uint256 newLimit); + error InvalidDestGasAmount(uint256 index, uint256 destGasAmount); error RootNotCommitted(); error CanOnlySelfCall(); error ReceiverError(bytes err); @@ -239,6 +241,23 @@ contract EVM2EVMOffRamp is IAny2EVMOffRamp, AggregateRateLimiter, ITypeAndVersio revert InvalidManualExecutionGasLimit(i, newLimit); } } + + if (report.messages[i].tokenAmounts.length != gasLimitOverrides[i].destGasAmounts.length) { + revert DestinationGasAmountCountMismatch(report.messages[i].messageId, report.messages[i].sequenceNumber); + } + + bytes[] memory encodedSourceTokenData = report.messages[i].sourceTokenData; + + for (uint256 j = 0; j < report.messages[i].tokenAmounts.length; ++j) { + Internal.SourceTokenData memory sourceTokenData = + abi.decode(encodedSourceTokenData[i], (Internal.SourceTokenData)); + uint256 destGasAmount = gasLimitOverrides[i].destGasAmounts[j]; + if (destGasAmount != 0) { + if (destGasAmount < sourceTokenData.destGasAmount) { + revert InvalidDestGasAmount(j, destGasAmount); + } + } + } } _execute(report, gasLimitOverrides); diff --git a/contracts/src/v0.8/ccip/test/helpers/receivers/ReentrancyAbuser.sol b/contracts/src/v0.8/ccip/test/helpers/receivers/ReentrancyAbuser.sol index 7e0980c8e9..42d4103a1d 100644 --- a/contracts/src/v0.8/ccip/test/helpers/receivers/ReentrancyAbuser.sol +++ b/contracts/src/v0.8/ccip/test/helpers/receivers/ReentrancyAbuser.sol @@ -9,6 +9,8 @@ import {EVM2EVMOffRamp} from "../../../offRamp/EVM2EVMOffRamp.sol"; contract ReentrancyAbuser is CCIPReceiver { event ReentrancySucceeded(); + uint32 internal constant DEFAULT_TOKEN_DEST_GAS_OVERHEAD = 144_000; + bool internal s_ReentrancyDone = false; Internal.ExecutionReport internal s_payload; EVM2EVMOffRamp internal s_offRamp; @@ -24,7 +26,7 @@ contract ReentrancyAbuser is CCIPReceiver { function _ccipReceive(Client.Any2EVMMessage memory) internal override { // Use original message gas limits in manual execution uint256 numMsgs = s_payload.messages.length; - EVM2EVMOffRamp.GasLimitOverride[] memory gasOverrides = new EVM2EVMOffRamp.GasLimitOverride[](numMsgs); + EVM2EVMOffRamp.GasLimitOverride[] memory gasOverrides = _getGasLimitsFromMessages(s_payload.messages); if (!s_ReentrancyDone) { // Could do more rounds but a PoC one is enough @@ -34,4 +36,24 @@ contract ReentrancyAbuser is CCIPReceiver { emit ReentrancySucceeded(); } } + + function _getGasLimitsFromMessages(Internal.EVM2EVMMessage[] memory messages) + internal + view + returns (EVM2EVMOffRamp.GasLimitOverride[] memory) + { + EVM2EVMOffRamp.GasLimitOverride[] memory gasLimitOverrides = new EVM2EVMOffRamp.GasLimitOverride[](messages.length); + for (uint256 i = 0; i < messages.length; ++i) { + gasLimitOverrides[i].receiverExecutionGasLimit = messages[i].gasLimit; + //create an array for destinationGasAmounts + gasLimitOverrides[i].destGasAmounts = new uint256[](messages[i].tokenAmounts.length); + + // initialize destGasAmounts + for (uint256 j = 0; j < messages[i].tokenAmounts.length; ++j) { + gasLimitOverrides[i].destGasAmounts[j] = DEFAULT_TOKEN_DEST_GAS_OVERHEAD + 1; + } + } + + return gasLimitOverrides; + } } diff --git a/contracts/src/v0.8/ccip/test/offRamp/EVM2EVMOffRamp.t.sol b/contracts/src/v0.8/ccip/test/offRamp/EVM2EVMOffRamp.t.sol index a8adf57834..e0d561dc56 100644 --- a/contracts/src/v0.8/ccip/test/offRamp/EVM2EVMOffRamp.t.sol +++ b/contracts/src/v0.8/ccip/test/offRamp/EVM2EVMOffRamp.t.sol @@ -1204,7 +1204,8 @@ contract EVM2EVMOffRamp_manuallyExecute is EVM2EVMOffRampSetup { messages[0].sequenceNumber, messages[0].messageId, Internal.MessageExecutionState.SUCCESS, "" ); - s_offRamp.manuallyExecute(report, _getGasLimitsFromMessages(messages)); + EVM2EVMOffRamp.GasLimitOverride[] memory gasLimits = _getGasLimitsFromMessages(messages); + s_offRamp.manuallyExecute(report, gasLimits); // Assert that they only got the tokens once, not twice assertEq(tokenToAbuse.balanceOf(address(receiver)), balancePre + tokenAmount); diff --git a/contracts/src/v0.8/ccip/test/offRamp/EVM2EVMOffRampSetup.t.sol b/contracts/src/v0.8/ccip/test/offRamp/EVM2EVMOffRampSetup.t.sol index 54846147ee..473f4f38af 100644 --- a/contracts/src/v0.8/ccip/test/offRamp/EVM2EVMOffRampSetup.t.sol +++ b/contracts/src/v0.8/ccip/test/offRamp/EVM2EVMOffRampSetup.t.sol @@ -20,7 +20,6 @@ import {OCR2BaseSetup} from "../ocr/OCR2Base.t.sol"; import {PriceRegistrySetup} from "../priceRegistry/PriceRegistry.t.sol"; import {IERC20} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; -import "forge-std/console.sol"; contract EVM2EVMOffRampSetup is TokenSetup, PriceRegistrySetup, OCR2BaseSetup { MockCommitStore internal s_mockCommitStore; @@ -191,6 +190,15 @@ contract EVM2EVMOffRampSetup is TokenSetup, PriceRegistrySetup, OCR2BaseSetup { return messages; } + function _generateSingleBasicMessageWithTokens() internal view returns (Internal.EVM2EVMMessage[] memory) { + Internal.EVM2EVMMessage[] memory messages = new Internal.EVM2EVMMessage[](2); + Client.EVMTokenAmount[] memory tokenAmounts = getCastedSourceEVMTokenAmountsWithZeroAmounts(); + tokenAmounts[0].amount = 1e18; + tokenAmounts[1].amount = 5e18; + messages[0] = _generateAny2EVMMessage(1, tokenAmounts, false); + return messages; + } + function _generateMessagesWithTokens() internal view returns (Internal.EVM2EVMMessage[] memory) { Internal.EVM2EVMMessage[] memory messages = new Internal.EVM2EVMMessage[](2); Client.EVMTokenAmount[] memory tokenAmounts = getCastedSourceEVMTokenAmountsWithZeroAmounts(); @@ -223,7 +231,7 @@ contract EVM2EVMOffRampSetup is TokenSetup, PriceRegistrySetup, OCR2BaseSetup { function _getGasLimitsFromMessages(Internal.EVM2EVMMessage[] memory messages) internal - pure + view returns (EVM2EVMOffRamp.GasLimitOverride[] memory) { EVM2EVMOffRamp.GasLimitOverride[] memory gasLimitOverrides = new EVM2EVMOffRamp.GasLimitOverride[](messages.length); @@ -231,6 +239,11 @@ contract EVM2EVMOffRampSetup is TokenSetup, PriceRegistrySetup, OCR2BaseSetup { gasLimitOverrides[i].receiverExecutionGasLimit = messages[i].gasLimit; //create an array for destinationGasAmounts gasLimitOverrides[i].destGasAmounts = new uint256[](messages[i].tokenAmounts.length); + + // initialize destGasAmounts + for (uint256 j = 0; j < messages[i].tokenAmounts.length; ++j) { + gasLimitOverrides[i].destGasAmounts[j] = DEFAULT_TOKEN_DEST_GAS_OVERHEAD + 1; + } } return gasLimitOverrides; @@ -238,14 +251,13 @@ contract EVM2EVMOffRampSetup is TokenSetup, PriceRegistrySetup, OCR2BaseSetup { function _prepareInvalidGasLimitsFromMessages(Internal.EVM2EVMMessage[] memory messages) internal + pure returns (EVM2EVMOffRamp.GasLimitOverride[] memory) { EVM2EVMOffRamp.GasLimitOverride[] memory gasLimitOverrides = new EVM2EVMOffRamp.GasLimitOverride[](messages.length); for (uint256 i = 0; i < messages.length; ++i) { gasLimitOverrides[i].receiverExecutionGasLimit = messages[i].gasLimit; - console.log("tokenAmounts length is: ", messages[i].tokenAmounts.length); gasLimitOverrides[i].destGasAmounts = new uint256[](messages[i].tokenAmounts.length - 1); - console.log("destGasAmounts array length after reduction is: ", gasLimitOverrides[i].destGasAmounts.length); } return gasLimitOverrides;