Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 33 additions & 7 deletions contracts/evmx/helpers/AsyncPromise.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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();
}
Expand All @@ -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.
Expand Down
5 changes: 5 additions & 0 deletions contracts/evmx/interfaces/IPromise.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down