From dade5406c44e6bb82e6c593ad8b84270c1b2cab4 Mon Sep 17 00:00:00 2001 From: Ameesha Agrawal Date: Thu, 2 Oct 2025 16:43:34 +0530 Subject: [PATCH] feat: payload revert --- contracts/evmx/helpers/AsyncPromise.sol | 40 ++++++++++++++++++++----- contracts/evmx/interfaces/IPromise.sol | 5 ++++ 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/contracts/evmx/helpers/AsyncPromise.sol b/contracts/evmx/helpers/AsyncPromise.sol index f727947d..012b9a4e 100644 --- a/contracts/evmx/helpers/AsyncPromise.sol +++ b/contracts/evmx/helpers/AsyncPromise.sol @@ -38,8 +38,16 @@ abstract contract AsyncPromiseStorage is IPromise { /// @notice The callback data to be used when the promise is resolved. bytes public callbackData; - // slots [53-102] reserved for gap - uint256[50] _gap_after; + // slot 53 + /// @notice The revert handler selector of the promise + bytes4 public revertHandlerSelector; + + // slot 54 + /// @notice The revert handler data of the promise + bytes public revertHandlerData; + + // slots [55-102] reserved for gap + uint256[48] _gap_after; // slots 103-154 (51) reserved for addr resolver util } @@ -133,7 +141,14 @@ contract AsyncPromise is AsyncPromiseStorage, Initializable, AddressResolverUtil /// @dev Only callable by the watcher. /// @dev handleRevert function can be retried till it succeeds function _handleRevert(bytes32 payloadId_) internal { - try IAppGateway(localInvoker).handleRevert(payloadId_) {} catch { + bytes memory combinedCalldata = abi.encodePacked( + revertHandlerSelector, + abi.encode(revertHandlerData, payloadId_) + ); + + (bool success, , ) = localInvoker.tryCall(0, gasleft(), 0, combinedCalldata); + + if (!success) { // todo: in this case, promise will stay unresolved revert PromiseRevertFailed(); } @@ -143,20 +158,31 @@ contract AsyncPromise is AsyncPromiseStorage, Initializable, AddressResolverUtil /// @param selector_ The function selector for the callback. /// @param data_ The data to be passed to the callback. function then(bytes4 selector_, bytes memory data_) external override { - if (msg.sender != localInvoker) revert NotInvoker(); // if the promise is already set up, revert if (state != AsyncPromiseState.WAITING_FOR_CALLBACK_SELECTOR) { revert PromiseAlreadySetUp(); } - if (watcher__().latestAsyncPromise() != address(this)) revert NotLatestPromise(); - if (requestCount != watcher__().getCurrentRequestCount()) revert RequestCountMismatch(); - + _validate(); // if the promise is waiting for the callback selector, set it and update the state callbackSelector = selector_; callbackData = data_; state = AsyncPromiseState.WAITING_FOR_CALLBACK_EXECUTION; } + function error(bytes4 selector_, bytes memory data_) external override { + _validate(); + // if the promise is waiting for the callback selector, set it and update the state + revertHandlerSelector = selector_; + revertHandlerData = data_; + state = AsyncPromiseState.WAITING_FOR_CALLBACK_EXECUTION; + } + + function _validate() internal { + if (msg.sender != localInvoker) revert NotInvoker(); + if (watcher__().latestAsyncPromise() != address(this)) revert NotLatestPromise(); + if (requestCount != watcher__().getCurrentRequestCount()) revert RequestCountMismatch(); + } + /** * @notice Rescues funds from the contract if they are locked by mistake. This contract does not * theoretically need this function but it is added for safety. diff --git a/contracts/evmx/interfaces/IPromise.sol b/contracts/evmx/interfaces/IPromise.sol index f323ae69..c898e16e 100644 --- a/contracts/evmx/interfaces/IPromise.sol +++ b/contracts/evmx/interfaces/IPromise.sol @@ -32,6 +32,11 @@ interface IPromise { /// @param data_ The data to be passed to the callback. function then(bytes4 selector_, bytes memory data_) external; + /// @notice Sets the revert handler selector and data for the promise. + /// @param selector_ The function selector for the revert handler. + /// @param data_ The data to be passed to the revert handler. + function error(bytes4 selector_, bytes memory data_) external; + /// @notice Marks the promise as resolved and executes the callback if set. /// @dev Only callable by the watcher precompile. /// @param resolvedPromise_ The data returned from the async payload execution.