Skip to content
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

feat: add userOpFailed hook #447

Closed
wants to merge 1 commit into from
Closed
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
21 changes: 19 additions & 2 deletions src/contracts/atlas/Atlas.sol
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,15 @@ contract Atlas is Escrow, Factory {
);
} catch (bytes memory revertData) {
// Bubble up some specific errors
_handleErrors(revertData, _dConfig.callConfig);
bool _doUserOpFailedHook = _handleErrors(revertData, _dConfig.callConfig);

// If appropriate, execute the userOpFailed hook. If it reverts, let the whole metacall revert.
if (_doUserOpFailedHook) {
_executeUserOpFailedHook(
_buildUserOpFailedContext(_executionEnvironment, _bundler, _isSimulation), userOp
);
}

// Set lock to FullyLocked to prevent any reentrancy possibility
_setLockPhase(uint8(ExecutionPhase.FullyLocked));

Expand Down Expand Up @@ -320,7 +328,8 @@ contract Atlas is Escrow, Factory {
/// @notice Called at the end of `metacall` to bubble up specific error info in a revert.
/// @param revertData Revert data from a failure during the execution of the metacall.
/// @param callConfig The CallConfig of the current metacall tx.
function _handleErrors(bytes memory revertData, uint32 callConfig) internal view {
/// @return A boolean indicating whether the error was a UserOpFail, which should trigger the userOpFailed hook.
function _handleErrors(bytes memory revertData, uint32 callConfig) internal view returns (bool) {
bytes4 _errorSwitch = bytes4(revertData);
if (msg.sender == SIMULATOR) {
// Simulation
Expand All @@ -342,6 +351,11 @@ contract Atlas is Escrow, Factory {
revert PostOpsSimFail();
}
}
if (_errorSwitch == UserOpFail.selector) {
// TODO check callConfig.needsUserOpFailedHook() if we include that setting
// TODO ensure needsUserOpFailedHook and allowsReuseUserOps are mutually exclusive
return true;
}
if (_errorSwitch == UserNotFulfilled.selector) {
revert UserNotFulfilled();
}
Expand All @@ -355,6 +369,9 @@ contract Atlas is Escrow, Factory {
revert(0, 4)
}
}

// If we reach this, do not trigger the userOpFailed hook.
return false;
}

/// @notice Returns whether or not the execution environment address matches what's expected from the set of inputs.
Expand Down
20 changes: 20 additions & 0 deletions src/contracts/atlas/Escrow.sol
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,26 @@ abstract contract Escrow is AtlETH {
revert UserOpFail();
}

function _executeUserOpFailedHook(
Context memory ctx,
UserOperation calldata userOp
)
internal
withLockPhase(ExecutionPhase.UserOpFailed)
{
(bool _success,) = ctx.executionEnvironment.call(
abi.encodePacked(
abi.encodeCall(IExecutionEnvironment.userOpFailedWrapper, userOp),
ctx.setAndPack(ExecutionPhase.UserOpFailed)
)
);

if (!_success) {
if (ctx.isSimulation) revert UserOpFailedHookSimFail();
revert UserOpFailedHookFail();
}
}

/// @notice Checks if the trusted operation hash matches and sets the appropriate error bit if it doesn't.
/// @param dConfig Configuration data for the DApp involved, containing execution parameters and settings.
/// @param prevalidated Boolean flag indicating whether the SolverOperation has been prevalidated to skip certain
Expand Down
25 changes: 25 additions & 0 deletions src/contracts/atlas/SafetyLocks.sol
Original file line number Diff line number Diff line change
Expand Up @@ -89,4 +89,29 @@ abstract contract SafetyLocks is Storage {
callDepth: 0
});
}

function _buildUserOpFailedContext(
address executionEnvironment,
address bundler,
bool isSimulation
)
internal
pure
returns (Context memory)
{
return Context({
executionEnvironment: executionEnvironment,
userOpHash: bytes32(0),
bundler: bundler,
solverSuccessful: false,
paymentsSuccessful: false,
solverIndex: 0,
solverCount: 0,
phase: uint8(ExecutionPhase.UserOpFailed),
solverOutcome: 0,
bidFind: false,
isSimulation: isSimulation,
callDepth: 0
});
}
}
9 changes: 9 additions & 0 deletions src/contracts/common/ExecutionEnvironment.sol
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,15 @@ contract ExecutionEnvironment is Base {
}
}

function userOpFailedWrapper(UserOperation calldata userOp) external onlyAtlasEnvironment {
bytes memory _data = _forward(abi.encodeCall(IDAppControl.userOpFailedCall, userOp));
bool _success;

(_success,) = _control().delegatecall(_data);

if (!_success) revert AtlasErrors.UserOpFailedWrapperDelegatecallFail();
}

/// @notice The postOpsWrapper function may be called by Atlas as the last phase of a `metacall` transaction.
/// @dev This contract is called by the Atlas contract, and delegatecalls the DAppControl contract via the
/// corresponding `postOpsCall` function.
Expand Down
22 changes: 22 additions & 0 deletions src/contracts/dapp/ControlTemplate.sol
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,28 @@ abstract contract DAppControlTemplate {
// User exposure: Trustless
function _checkUserOperation(UserOperation memory) internal virtual { }

/////////////////////////////////////////////////////////
// USER OPERATION FAILED //
/////////////////////////////////////////////////////////
//
// UserOpFailedHook:
// Data should be decoded as:
//
// bytes memory userOpData
//

// _userOpFailedCall
// Details:
// userOpFailed/delegate =
// Inputs: User's calldata
// Function: Executing the function set by DAppControl
// Container: Inside of the FastLane ExecutionEnvironment
// Access: With storage access (read + write) only to the ExecutionEnvironment
//
// DApp exposure: Trustless
// User exposure: Trustless
function _userOpFailedCall(UserOperation calldata) internal virtual { }

/////////////////////////////////////////////////////////
// MEV ALLOCATION //
/////////////////////////////////////////////////////////
Expand Down
11 changes: 11 additions & 0 deletions src/contracts/dapp/DAppControl.sol
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,17 @@ abstract contract DAppControl is DAppControlTemplate, ExecutionBase {
return _preOpsCall(userOp);
}

/// @notice The userOpFailed hook which is called if the UserOperation fails, before the metacall tx ends.
/// @param userOp The UserOperation struct.
function userOpFailedCall(UserOperation calldata userOp)
external
validControl
onlyAtlasEnvironment
onlyPhase(ExecutionPhase.UserOpFailed)
{
_userOpFailedCall(userOp);
}

/// @notice The preSolverCall hook which may be called before the SolverOperation is executed.
/// @dev Should revert if any DApp-specific checks fail to indicate non-fulfillment.
/// @param solverOp The SolverOperation to be executed after this hook has been called.
Expand Down
2 changes: 2 additions & 0 deletions src/contracts/interfaces/IDAppControl.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ interface IDAppControl {

function allocateValueCall(address bidToken, uint256 bidAmount, bytes calldata data) external;

function userOpFailedCall(UserOperation calldata userOp) external;

function getDAppConfig(UserOperation calldata userOp) external view returns (DAppConfig memory dConfig);

function getCallConfig() external view returns (CallConfig memory callConfig);
Expand Down
2 changes: 2 additions & 0 deletions src/contracts/interfaces/IExecutionEnvironment.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ interface IExecutionEnvironment {

function userWrapper(UserOperation calldata userOp) external payable returns (bytes memory userReturnData);

function userOpFailedWrapper(UserOperation calldata userOp) external;

function postOpsWrapper(bool solved, bytes calldata returnData) external;

function solverPreTryCatch(
Expand Down
3 changes: 3 additions & 0 deletions src/contracts/types/AtlasErrors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ contract AtlasErrors {
error SolverSimFail(uint256 solverOutcomeResult); // uint param is result returned in `verifySolverOp`
error AllocateValueSimFail();
error PostOpsSimFail();
error UserOpFailedHookSimFail();
error ValidCalls(ValidCallsResult);

// Execution Environment
Expand All @@ -51,6 +52,7 @@ contract AtlasErrors {
error PostOpsDelegatecallFail();
error PostOpsDelegatecallReturnedFalse();
error AllocateValueDelegatecallFail();
error UserOpFailedWrapperDelegatecallFail();
error NotEnvironmentOwner();
error ExecutionEnvironmentBalanceTooLow();

Expand All @@ -60,6 +62,7 @@ contract AtlasErrors {
// error SolverFail(); // Only sim version of err is used
error AllocateValueFail();
error PostOpsFail();
error UserOpFailedHookFail();
error InvalidAccess();

// Escrow
Expand Down
1 change: 1 addition & 0 deletions src/contracts/types/LockTypes.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,6 @@ enum ExecutionPhase {
PostSolver,
AllocateValue,
PostOps,
UserOpFailed,
FullyLocked
}
Loading