-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathComposableExecutionModule.sol
More file actions
190 lines (170 loc) · 9.02 KB
/
ComposableExecutionModule.sol
File metadata and controls
190 lines (170 loc) · 9.02 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import { IExecutor } from "erc7579/interfaces/IERC7579Module.sol";
import { IERC7579Account, Execution } from "erc7579/interfaces/IERC7579Account.sol";
import { ModeLib } from "erc7579/lib/ModeLib.sol";
import { ExecutionLib } from "erc7579/lib/ExecutionLib.sol";
import { ERC7579FallbackBase } from "module-bases/ERC7579FallbackBase.sol";
import { IComposableExecutionModule } from "./interfaces/IComposableExecution.sol";
import { ComposableExecutionLib } from "./ComposableExecutionLib.sol";
import {
InputParam,
OutputParam,
ComposableExecution,
Constraint,
ConstraintType,
InputParamFetcherType,
OutputParamFetcherType
} from "./types/ComposabilityDataTypes.sol";
/**
* @title Composable Execution Module: Executor and Fallback
* @dev A module for ERC-7579 accounts that enables composable transactions execution
*/
contract ComposableExecutionModule is IComposableExecutionModule, IExecutor, ERC7579FallbackBase {
address private constant ENTRY_POINT_V07_ADDRESS = 0x0000000071727De22E5E9d8BAf0edAc6f37da032;
address public immutable DEFAULT_EP_ADDRESS;
address private immutable THIS_ADDRESS;
using ComposableExecutionLib for InputParam[];
using ComposableExecutionLib for OutputParam[];
error OnlyEntryPointOrAccount();
error ZeroAddressNotAllowed();
error FailedToReturnMsgValue();
error DelegateCallOnly();
/// @notice Mapping of smart account addresses to the EP address
mapping(address => address) private entryPoints;
constructor(address _defaultEpAddress) {
if (_defaultEpAddress == address(0)) {
DEFAULT_EP_ADDRESS = ENTRY_POINT_V07_ADDRESS;
} else {
DEFAULT_EP_ADDRESS = _defaultEpAddress;
}
THIS_ADDRESS = address(this);
}
/**
* @notice Executes a composable transaction with dynamic parameter composition and return value handling
* @dev To be used via fallback() from the account
* @dev As per ERC-7579 account MUST append original msg.sender address to the calldata in a way specified by ERC-2771
* @dev Returns the msg.value back to the sender (account) if any. This is done because in most cases the SA.fallback
* forwards value to this module. This allows SA receiving value along with the composable execution call processed via fallback.
*/
function executeComposable(ComposableExecution[] calldata cExecutions) external payable {
// access control
address sender = _msgSender();
// in most cases, only first condition (against constant) will be checked
// so no extra sloads
require(sender == DEFAULT_EP_ADDRESS || sender == entryPoints[msg.sender] || sender == msg.sender, OnlyEntryPointOrAccount());
_returnMsgValue();
_executeComposable(cExecutions, msg.sender, _executeExecutionCall);
}
/// @notice It doesn't require access control as it is expected to be called by the account itself via .execute()
/// @dev !!! Attention !!! This function should NEVER be installed to be used via fallback() as it doesn't implement access control
/// thus it will be callable by any address account.executeComposableCall => fallback() => this.executeComposableCall
function executeComposableCall(ComposableExecution[] calldata cExecutions) external {
_executeComposable(cExecutions, msg.sender, _executeExecutionCall);
}
/// @notice It doesn't require access control as it is expected to be called by the account itself via .execute(mode = delegatecall)
function executeComposableDelegateCall(ComposableExecution[] calldata cExecutions) external {
require(THIS_ADDRESS != address(this), DelegateCallOnly());
_executeComposable(cExecutions, address(this), _executeExecutionDelegatecall);
}
/// @dev internal function to execute the composable execution flow
/// @param cExecutions - the composable executions to execute
/// @param account - the account to execute the composable executions on
/// @param executeExecutionFunction - the function to execute the composable executions
function _executeComposable(
ComposableExecution[] calldata cExecutions,
address account,
function(Execution memory execution) internal returns (bytes[] memory) executeExecutionFunction
)
internal
{
// we can not use erc-7579 batch mode here because we may need to compose
// the next call in the batch based on the execution result of the previous call
uint256 length = cExecutions.length;
for (uint256 i; i < length; i++) {
ComposableExecution calldata cExecution = cExecutions[i];
Execution memory execution = cExecution.inputParams.processInputs(cExecution.functionSig);
bytes[] memory returnData;
if (execution.target != address(0)) {
returnData = executeExecutionFunction(execution);
} else {
returnData = new bytes[](1);
returnData[0] = "";
}
cExecution.outputParams.processOutputs(returnData[0], account);
}
}
/// @dev function to be used as an argument for _executeComposable in case of regular call
function _executeExecutionCall(Execution memory execution) internal returns (bytes[] memory) {
return IERC7579Account(msg.sender)
.executeFromExecutor({
mode: ModeLib.encodeSimpleSingle(), executionCalldata: ExecutionLib.encodeSingle(execution.target, execution.value, execution.callData)
});
}
/// @dev function to be used as an argument for _executeComposable in case of delegatecall
function _executeExecutionDelegatecall(Execution memory execution) internal returns (bytes[] memory returnData) {
returnData = new bytes[](1);
returnData[0] = _execute(execution.target, execution.value, execution.callData);
}
/// @dev sets the entry point for the account
function setEntryPoint(address _entryPoint) external {
require(_entryPoint != address(0), ZeroAddressNotAllowed());
entryPoints[msg.sender] = _entryPoint;
}
/// @dev returns the entry point address
function getEntryPoint(address account) external view returns (address) {
return entryPoints[account] == address(0) ? DEFAULT_EP_ADDRESS : entryPoints[account];
}
/// @dev called when the module is installed
/// @dev expected behavior: reverts if tried to initialize the module for the same account more than once
/// inner require checks if some account just sends same data for both fallback and executor
function onInstall(bytes calldata data) external override {
if (data.length >= 20) {
if (entryPoints[msg.sender] != address(0)) {
require(entryPoints[msg.sender] == address(bytes20(data[0:20])), AlreadyInitialized(msg.sender));
return;
}
entryPoints[msg.sender] = address(bytes20(data[0:20]));
}
}
/// @dev returns true if the module is initialized for the given account
function isInitialized(address account) external view returns (bool) {
return entryPoints[account] != address(0);
}
/// @dev called when the module is uninstalled
function onUninstall(bytes calldata data) external override {
delete entryPoints[msg.sender];
}
/// @dev Reports that this module is an executor and a fallback module
function isModuleType(uint256 moduleTypeId) external pure override returns (bool) {
return moduleTypeId == TYPE_EXECUTOR || moduleTypeId == TYPE_FALLBACK;
}
/// @notice Executes a call to a target address with specified value and data.
/// @notice calls to an EOA should be counted as successful.
/// @param target The address to execute the call on.
/// @param value The amount of wei to send with the call.
/// @param callData The calldata to send.
/// @return result The bytes returned from the execution, which contains the returned data from the target address.
function _execute(address target, uint256 value, bytes memory callData) internal virtual returns (bytes memory result) {
/// @solidity memory-safe-assembly
assembly {
result := mload(0x40)
if iszero(call(gas(), target, value, add(callData, 0x20), mload(callData), codesize(), 0x00)) {
// Bubble up the revert if the call reverts.
returndatacopy(result, 0x00, returndatasize())
revert(result, returndatasize())
}
mstore(result, returndatasize()) // Store the length.
let o := add(result, 0x20)
returndatacopy(o, 0x00, returndatasize()) // Copy the returndata.
mstore(0x40, add(o, returndatasize())) // Allocate the memory.
}
}
// Returns the msg.value back to the sender (account)
function _returnMsgValue() internal {
if (msg.value > 0) {
(bool success,) = payable(msg.sender).call{ value: msg.value }("");
require(success, FailedToReturnMsgValue());
}
}
}