-
Notifications
You must be signed in to change notification settings - Fork 54
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Manual-execute-gas-overrides #1256
Changes from 64 commits
da2b0b9
09719c0
db6de00
449a3b3
fef0df1
641d29e
5c8b538
e8763c5
7ffcd20
dbccb54
ddb384a
34efe99
fea911a
dad0478
339ab36
12db93e
09d0248
b2bbe11
7356a8e
fb7d2b3
ad0aba6
d21ec9e
c4d19cc
cd3c1a2
84402a9
931d5c9
30b37ec
5160115
7028235
3891da0
d187fe5
48d3566
64cb4cb
099a789
3a31c8d
6177ab9
55d2894
0364e3a
9da075a
c827fd4
21e70e1
411c66b
6849697
74ab3e8
1deda6d
f26d0fc
086778c
e5c96c4
42a4adc
5bc5f42
803235f
a48f15f
d9793ed
c18a87f
1a10b1d
4b76aad
c1258c5
2a10db1
9542dca
20dcec5
2d56bad
666f38e
09a960c
40200c6
bae670c
18bafa2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -45,7 +45,9 @@ contract EVM2EVMOffRamp is IAny2EVMOffRamp, AggregateRateLimiter, ITypeAndVersio | |
error UnsupportedNumberOfTokens(uint64 sequenceNumber); | ||
error ManualExecutionNotYetEnabled(); | ||
error ManualExecutionGasLimitMismatch(); | ||
error InvalidManualExecutionGasLimit(uint256 index, uint256 newLimit); | ||
error DestinationGasAmountCountMismatch(bytes32 messageId, uint64 sequenceNumber); | ||
error InvalidManualExecutionGasLimit(bytes32 messageId, uint256 newLimit); | ||
RensR marked this conversation as resolved.
Show resolved
Hide resolved
|
||
error InvalidTokenGasOverride(bytes32 messageId, uint256 tokenIndex, uint256 tokenGasOverride); | ||
error RootNotCommitted(); | ||
error CanOnlySelfCall(); | ||
error ReceiverError(bytes err); | ||
|
@@ -100,6 +102,11 @@ contract EVM2EVMOffRamp is IAny2EVMOffRamp, AggregateRateLimiter, ITypeAndVersio | |
address destToken; | ||
} | ||
|
||
struct GasLimitOverride { | ||
uint256 receiverExecutionGasLimit; | ||
uint32[] tokenGasOverrides; | ||
} | ||
RayXpub marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
// STATIC CONFIG | ||
string public constant override typeAndVersion = "EVM2EVMOffRamp 1.5.0-dev"; | ||
|
||
|
@@ -218,18 +225,44 @@ contract EVM2EVMOffRamp is IAny2EVMOffRamp, AggregateRateLimiter, ITypeAndVersio | |
/// @param gasLimitOverrides New gasLimit for each message in the report. | ||
/// @dev We permit gas limit overrides so that users may manually execute messages which failed due to | ||
/// insufficient gas provided. | ||
function manuallyExecute(Internal.ExecutionReport memory report, uint256[] memory gasLimitOverrides) external { | ||
function manuallyExecute( | ||
Internal.ExecutionReport memory report, | ||
GasLimitOverride[] memory gasLimitOverrides | ||
) external { | ||
// We do this here because the other _execute path is already covered OCR2BaseXXX. | ||
_checkChainForked(); | ||
|
||
uint256 numMsgs = report.messages.length; | ||
if (numMsgs != gasLimitOverrides.length) revert ManualExecutionGasLimitMismatch(); | ||
for (uint256 i = 0; i < numMsgs; ++i) { | ||
uint256 newLimit = gasLimitOverrides[i]; | ||
Internal.EVM2EVMMessage memory message = report.messages[i]; | ||
GasLimitOverride memory gasLimitOverride = gasLimitOverrides[i]; | ||
|
||
uint256 newLimit = gasLimitOverride.receiverExecutionGasLimit; | ||
// Checks to ensure message cannot be executed with less gas than specified. | ||
if (newLimit != 0) { | ||
if (newLimit < report.messages[i].gasLimit) { | ||
revert InvalidManualExecutionGasLimit(i, newLimit); | ||
if (newLimit < message.gasLimit) { | ||
revert InvalidManualExecutionGasLimit(message.messageId, newLimit); | ||
} | ||
} | ||
|
||
if (message.tokenAmounts.length != gasLimitOverride.tokenGasOverrides.length) { | ||
revert DestinationGasAmountCountMismatch(message.messageId, message.sequenceNumber); | ||
} | ||
|
||
bytes[] memory encodedSourceTokenData = message.sourceTokenData; | ||
|
||
for (uint256 j = 0; j < message.tokenAmounts.length; ++j) { | ||
Internal.SourceTokenData memory sourceTokenData = | ||
abi.decode(encodedSourceTokenData[i], (Internal.SourceTokenData)); | ||
uint256 tokenGasOverride = gasLimitOverride.tokenGasOverrides[j]; | ||
|
||
// The gas limit can not be lowered as that could cause the message to fail. If manual execution is done | ||
// from an UNTOUCHED state and we would allow lower gas limit, anyone could grief by executing the message with | ||
// lower gas limit than the DON would have used. This results in the message being marked FAILURE and the DON | ||
// would not attempt it with the correct gas limit. | ||
if (tokenGasOverride != 0 && tokenGasOverride < sourceTokenData.destGasAmount) { | ||
revert InvalidTokenGasOverride(message.messageId, j, tokenGasOverride); | ||
} | ||
} | ||
} | ||
|
@@ -240,15 +273,15 @@ contract EVM2EVMOffRamp is IAny2EVMOffRamp, AggregateRateLimiter, ITypeAndVersio | |
/// @notice Entrypoint for execution, called by the OCR network | ||
/// @dev Expects an encoded ExecutionReport | ||
function _report(bytes calldata report) internal override { | ||
_execute(abi.decode(report, (Internal.ExecutionReport)), new uint256[](0)); | ||
_execute(abi.decode(report, (Internal.ExecutionReport)), new GasLimitOverride[](0)); | ||
} | ||
|
||
/// @notice Executes a report, executing each message in order. | ||
/// @param report The execution report containing the messages and proofs. | ||
/// @param manualExecGasLimits An array of gas limits to use for manual execution. | ||
/// @dev If called from the DON, this array is always empty. | ||
/// @dev If called from manual execution, this array is always same length as messages. | ||
function _execute(Internal.ExecutionReport memory report, uint256[] memory manualExecGasLimits) internal { | ||
function _execute(Internal.ExecutionReport memory report, GasLimitOverride[] memory manualExecGasLimits) internal { | ||
RensR marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (IRMN(i_rmnProxy).isCursed(bytes16(uint128(i_sourceChainSelector)))) revert CursedByRMN(); | ||
|
||
uint256 numMsgs = report.messages.length; | ||
|
@@ -273,7 +306,6 @@ contract EVM2EVMOffRamp is IAny2EVMOffRamp, AggregateRateLimiter, ITypeAndVersio | |
if (timestampCommitted == 0) revert RootNotCommitted(); | ||
|
||
// Execute messages | ||
bool manualExecution = manualExecGasLimits.length != 0; | ||
for (uint256 i = 0; i < numMsgs; ++i) { | ||
Internal.EVM2EVMMessage memory message = report.messages[i]; | ||
Internal.MessageExecutionState originalState = getExecutionState(message.sequenceNumber); | ||
|
@@ -292,8 +324,11 @@ contract EVM2EVMOffRamp is IAny2EVMOffRamp, AggregateRateLimiter, ITypeAndVersio | |
emit SkippedAlreadyExecutedMessage(message.sequenceNumber); | ||
continue; | ||
} | ||
uint32[] memory tokenGasOverrides; | ||
bool manualExecution = manualExecGasLimits.length != 0; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why was this moved into the loop, is it cheaper this way? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doesn't change much but moved outside as it saves a tiny bit |
||
|
||
if (manualExecution) { | ||
tokenGasOverrides = manualExecGasLimits[i].tokenGasOverrides; | ||
bool isOldCommitReport = | ||
(block.timestamp - timestampCommitted) > s_dynamicConfig.permissionLessExecutionThresholdSeconds; | ||
// Manually execution is fine if we previously failed or if the commit report is just too old | ||
|
@@ -303,8 +338,8 @@ contract EVM2EVMOffRamp is IAny2EVMOffRamp, AggregateRateLimiter, ITypeAndVersio | |
} | ||
|
||
// Manual execution gas limit can override gas limit specified in the message. Value of 0 indicates no override. | ||
if (manualExecGasLimits[i] != 0) { | ||
message.gasLimit = manualExecGasLimits[i]; | ||
if (manualExecGasLimits[i].receiverExecutionGasLimit != 0) { | ||
message.gasLimit = manualExecGasLimits[i].receiverExecutionGasLimit; | ||
} | ||
} else { | ||
// DON can only execute a message once | ||
|
@@ -361,7 +396,8 @@ contract EVM2EVMOffRamp is IAny2EVMOffRamp, AggregateRateLimiter, ITypeAndVersio | |
); | ||
|
||
_setExecutionState(message.sequenceNumber, Internal.MessageExecutionState.IN_PROGRESS); | ||
(Internal.MessageExecutionState newState, bytes memory returnData) = _trialExecute(message, offchainTokenData); | ||
(Internal.MessageExecutionState newState, bytes memory returnData) = | ||
_trialExecute(message, offchainTokenData, tokenGasOverrides); | ||
_setExecutionState(message.sequenceNumber, newState); | ||
|
||
// Since it's hard to estimate whether manual execution will succeed, we | ||
|
@@ -432,9 +468,10 @@ contract EVM2EVMOffRamp is IAny2EVMOffRamp, AggregateRateLimiter, ITypeAndVersio | |
/// @return revert data in bytes if CCIP receiver reverted during execution. | ||
function _trialExecute( | ||
Internal.EVM2EVMMessage memory message, | ||
bytes[] memory offchainTokenData | ||
bytes[] memory offchainTokenData, | ||
uint32[] memory tokenGasOverrides | ||
) internal returns (Internal.MessageExecutionState, bytes memory) { | ||
try this.executeSingleMessage(message, offchainTokenData) {} | ||
try this.executeSingleMessage(message, offchainTokenData, tokenGasOverrides) {} | ||
catch (bytes memory err) { | ||
// return the message execution state as FAILURE and the revert data | ||
// Max length of revert data is Router.MAX_RET_BYTES, max length of err is 4 + Router.MAX_RET_BYTES | ||
|
@@ -451,12 +488,21 @@ contract EVM2EVMOffRamp is IAny2EVMOffRamp, AggregateRateLimiter, ITypeAndVersio | |
/// its execution and enforce atomicity among successful message processing and token transfer. | ||
/// @dev We use ERC-165 to check for the ccipReceive interface to permit sending tokens to contracts | ||
/// (for example smart contract wallets) without an associated message. | ||
function executeSingleMessage(Internal.EVM2EVMMessage calldata message, bytes[] calldata offchainTokenData) external { | ||
function executeSingleMessage( | ||
Internal.EVM2EVMMessage calldata message, | ||
bytes[] calldata offchainTokenData, | ||
uint32[] memory tokenGasOverrides | ||
) external { | ||
if (msg.sender != address(this)) revert CanOnlySelfCall(); | ||
Client.EVMTokenAmount[] memory destTokenAmounts = new Client.EVMTokenAmount[](0); | ||
if (message.tokenAmounts.length > 0) { | ||
destTokenAmounts = _releaseOrMintTokens( | ||
message.tokenAmounts, abi.encode(message.sender), message.receiver, message.sourceTokenData, offchainTokenData | ||
message.tokenAmounts, | ||
abi.encode(message.sender), | ||
message.receiver, | ||
message.sourceTokenData, | ||
offchainTokenData, | ||
tokenGasOverrides | ||
); | ||
} | ||
// There are three cases in which we skip calling the receiver: | ||
|
@@ -701,19 +747,27 @@ contract EVM2EVMOffRamp is IAny2EVMOffRamp, AggregateRateLimiter, ITypeAndVersio | |
bytes memory originalSender, | ||
address receiver, | ||
bytes[] calldata encodedSourceTokenData, | ||
bytes[] calldata offchainTokenData | ||
bytes[] calldata offchainTokenData, | ||
uint32[] memory tokenGasOverrides | ||
) internal returns (Client.EVMTokenAmount[] memory destTokenAmounts) { | ||
// Creating a copy is more gas efficient than initializing a new array. | ||
destTokenAmounts = sourceTokenAmounts; | ||
uint256 value = 0; | ||
for (uint256 i = 0; i < sourceTokenAmounts.length; ++i) { | ||
Internal.SourceTokenData memory sourceTokenData = | ||
abi.decode(encodedSourceTokenData[i], (Internal.SourceTokenData)); | ||
if (tokenGasOverrides.length != 0) { | ||
if (tokenGasOverrides[i] != 0) { | ||
sourceTokenData.destGasAmount = tokenGasOverrides[i]; | ||
} | ||
} | ||
destTokenAmounts[i] = _releaseOrMintToken( | ||
sourceTokenAmounts[i].amount, | ||
originalSender, | ||
receiver, | ||
// This should never revert as the onRamp encodes the sourceTokenData struct. Only the inner components from | ||
// this struct come from untrusted sources. | ||
abi.decode(encodedSourceTokenData[i], (Internal.SourceTokenData)), | ||
sourceTokenData, | ||
offchainTokenData[i] | ||
); | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Needed due to space issues