Skip to content

Commit af610f8

Browse files
committed
add composability constraints + tests; make compiler version fixed; update pm codehash to reflect a new compiler version;
1 parent 03ce607 commit af610f8

5 files changed

Lines changed: 279 additions & 150 deletions

File tree

contracts/composability/ComposableExecutionLib.sol

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,14 +70,14 @@ library ComposableExecutionLib {
7070
// TODO: change all abi.decodes to calldata slicing
7171
function processInput(InputParam calldata param) internal view returns (bytes memory) {
7272
if (param.fetcherType == InputParamFetcherType.RAW_BYTES) {
73-
return param.paramData;
73+
return _validateConstraints(param.paramData, param.constraints);
7474
} else if (param.fetcherType == InputParamFetcherType.STATIC_CALL) {
7575
(address contractAddr, bytes memory callData) = abi.decode(param.paramData, (address, bytes));
7676
(bool success, bytes memory returnData) = contractAddr.staticcall(callData);
7777
if (!success) {
7878
revert ExecutionFailed();
7979
}
80-
return returnData;
80+
return _validateConstraints(returnData, param.constraints);
8181
} else {
8282
revert InvalidParameterEncoding();
8383
}
@@ -119,8 +119,8 @@ library ComposableExecutionLib {
119119
}
120120
}
121121

122-
function _validateContraints(bytes memory rawValue, bytes calldata constraints) private pure {
123-
if (constraints.length == 0) { return; }
122+
function _validateConstraints(bytes memory rawValue, bytes calldata constraints) private pure returns (bytes memory) {
123+
if (constraints.length == 0) { return rawValue; }
124124
bytes1 constraintType = bytes1(constraints[0:1]);
125125
bytes32 rawValueBytes32 = bytes32(rawValue);
126126

@@ -142,6 +142,8 @@ library ComposableExecutionLib {
142142
else {
143143
revert InvalidConstraintType();
144144
}
145+
146+
return rawValue;
145147
}
146148

147149
}

foundry.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
[profile.default]
2-
evm_version = "cancun"
2+
evm_version = "prague"
33
src = "contracts"
44
out = "out"
55
solc = "0.8.27"
6-
auto_detect_solc = true
6+
#auto_detect_solc = true
77
libs = ["node_modules", "lib"]
8-
via-ir = false
8+
via-ir = true
99

1010
# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options

test/Base.t.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ contract BaseTest is Test {
4747
using CopyUserOpLib for PackedUserOperation;
4848
using LibZip for bytes;
4949

50-
bytes32 constant NODE_PM_CODE_HASH = 0xc6f0a03003eadb3366120ed0517bb39c35edbbaa996d3980c23ba245eae408dd;
50+
bytes32 constant NODE_PM_CODE_HASH = 0x0db2dd078c466b34fb169920f4f06f4a32801b1a62cfdaff960892c924c0f117;
5151

5252
address constant ENTRYPOINT_V07_ADDRESS = 0x0000000071727De22E5E9d8BAf0edAc6f37da032;
5353
address constant MEE_NODE_ADDRESS = 0x177EE170D31177Ee170D31177ee170d31177eE17;

test/unit/ComposableExecution.t.sol

Lines changed: 269 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {ComposableExecutionModule} from "contracts/composability/ComposableExecu
77
import {Storage} from "contracts/composability/Storage.sol";
88
import {IComposableExecution} from "contracts/interfaces/IComposableExecution.sol";
99
import {ComposableExecution, InputParam, OutputParam, ParamValueType, OutputParamFetcherType, InputParamFetcherType} from "contracts/composability/ComposableExecutionLib.sol";
10+
import { CONSTRAINT_TYPE_EQ, CONSTRAINT_TYPE_GTE, CONSTRAINT_TYPE_LTE, CONSTRAINT_TYPE_IN } from "contracts/types/Constants.sol";
1011

1112
event Uint256Emitted(uint256 value);
1213
event Uint256Emitted2(uint256 value1, uint256 value2);
@@ -110,12 +111,268 @@ contract ComposableExecutionTest is ComposabilityTestBase {
110111
_outputExecResultAddress(address(mockAccount), address(mockAccount));
111112
}
112113

114+
function test_inputsWithConstraints() public {
115+
_inputParamUsingGteConstraints(address(mockAccount), address(mockAccount));
116+
_inputParamUsingLteConstraints(address(mockAccount), address(mockAccount));
117+
_inputParamUsingInConstraints(address(mockAccount), address(mockAccount));
118+
_inputParamUsingEqConstraints(address(mockAccount), address(mockAccount));
119+
}
120+
113121
// TODO:
114122
// test that input value works correctly with all types: address, bool.
115123

116124

117125
// ================================ TEST SCENARIOS ================================
118126

127+
function _inputParamUsingGteConstraints(address account, address caller) internal {
128+
vm.startPrank(ENTRYPOINT_V07_ADDRESS);
129+
130+
// Prepare invalid input param - call should revert
131+
InputParam[] memory invalidInputParams = new InputParam[](1);
132+
invalidInputParams[0] = InputParam({
133+
fetcherType: InputParamFetcherType.RAW_BYTES,
134+
valueType: ParamValueType.UINT256,
135+
paramData: abi.encode(42),
136+
constraints: abi.encodePacked(CONSTRAINT_TYPE_GTE, bytes32(uint256(43))) // value must be >= 43 but 42 provided
137+
});
138+
139+
// Prepare valid input param - call should succeed
140+
InputParam[] memory validInputParams = new InputParam[](1);
141+
validInputParams[0] = InputParam({
142+
fetcherType: InputParamFetcherType.RAW_BYTES,
143+
valueType: ParamValueType.UINT256,
144+
paramData: abi.encode(43),
145+
constraints: abi.encodePacked(CONSTRAINT_TYPE_GTE, bytes32(uint256(43))) // value must be >= 42
146+
});
147+
148+
OutputParam[] memory outputParams = new OutputParam[](1);
149+
outputParams[0] = OutputParam({
150+
fetcherType: OutputParamFetcherType.STATIC_CALL,
151+
valueType: ParamValueType.UINT256,
152+
paramData: abi.encode(address(dummyContract), abi.encodeWithSelector(DummyContract.A.selector), address(storageContract), SLOT_A)
153+
});
154+
155+
// Call empty function and it should revert because dynamic param value doesnt meet constraints
156+
ComposableExecution[] memory failingExecutions = new ComposableExecution[](1);
157+
failingExecutions[0] = ComposableExecution({
158+
to: address(0), // no function call
159+
value: 0, // no value sent
160+
functionSig: "", // no calldata encoded
161+
inputParams: invalidInputParams, // use constrainted input parameter that's going to fail
162+
outputParams: outputParams
163+
});
164+
vm.expectRevert(bytes("GTE constraint not met."));
165+
IComposableExecution(address(account)).executeComposable(failingExecutions);
166+
167+
// Call empty function and it should NOT revert because dynamic param value meets constraints
168+
ComposableExecution[] memory validExecutions = new ComposableExecution[](1);
169+
validExecutions[0] = ComposableExecution({
170+
to: address(0), // no function call
171+
value: 0, // no value sent
172+
functionSig: "", // no calldata encoded
173+
inputParams: validInputParams, // use valid input params
174+
outputParams: outputParams
175+
});
176+
IComposableExecution(address(account)).executeComposable(validExecutions);
177+
178+
// Expect valid composable execution to store result as per given output params
179+
bytes32 namespace = storageContract.getNamespace(address(account), address(caller));
180+
bytes32 storedValueA = storageContract.readStorage(namespace, SLOT_A);
181+
assertEq(uint256(storedValueA), 42, "Function A result not stored correctly");
182+
}
183+
184+
function _inputParamUsingLteConstraints(address account, address caller) internal {
185+
vm.startPrank(ENTRYPOINT_V07_ADDRESS);
186+
187+
// Prepare invalid input param - call should revert
188+
InputParam[] memory invalidInputParams = new InputParam[](1);
189+
invalidInputParams[0] = InputParam({
190+
fetcherType: InputParamFetcherType.RAW_BYTES,
191+
valueType: ParamValueType.UINT256,
192+
paramData: abi.encode(42),
193+
constraints: abi.encodePacked(CONSTRAINT_TYPE_LTE, bytes32(uint256(41))) // value must be <= 41 but 42 provided
194+
});
195+
196+
// Prepare valid input param - call should succeed
197+
InputParam[] memory validInputParams = new InputParam[](1);
198+
validInputParams[0] = InputParam({
199+
fetcherType: InputParamFetcherType.RAW_BYTES,
200+
valueType: ParamValueType.UINT256,
201+
paramData: abi.encode(41),
202+
constraints: abi.encodePacked(CONSTRAINT_TYPE_LTE, bytes32(uint256(41))) // value must be <= 41
203+
});
204+
205+
OutputParam[] memory outputParams = new OutputParam[](1);
206+
outputParams[0] = OutputParam({
207+
fetcherType: OutputParamFetcherType.STATIC_CALL,
208+
valueType: ParamValueType.UINT256,
209+
paramData: abi.encode(address(dummyContract), abi.encodeWithSelector(DummyContract.A.selector), address(storageContract), SLOT_A)
210+
});
211+
212+
// Call empty function and it should revert because dynamic param value doesnt meet constraints
213+
ComposableExecution[] memory failingExecutions = new ComposableExecution[](1);
214+
failingExecutions[0] = ComposableExecution({
215+
to: address(0), // no function call
216+
value: 0, // no value sent
217+
functionSig: "", // no calldata encoded
218+
inputParams: invalidInputParams, // use constrainted input parameter that's going to fail
219+
outputParams: outputParams
220+
});
221+
vm.expectRevert(bytes("LTE constraint not met."));
222+
IComposableExecution(address(account)).executeComposable(failingExecutions);
223+
224+
// Call empty function and it should NOT revert because dynamic param value meets constraints
225+
ComposableExecution[] memory validExecutions = new ComposableExecution[](1);
226+
validExecutions[0] = ComposableExecution({
227+
to: address(0), // no function call
228+
value: 0, // no value sent
229+
functionSig: "", // no calldata encoded
230+
inputParams: validInputParams, // use valid input params
231+
outputParams: outputParams
232+
});
233+
IComposableExecution(address(account)).executeComposable(validExecutions);
234+
235+
// Expect valid composable execution to store result as per given output params
236+
bytes32 namespace = storageContract.getNamespace(address(account), address(caller));
237+
bytes32 storedValueA = storageContract.readStorage(namespace, SLOT_A);
238+
assertEq(uint256(storedValueA), 42, "Function A result not stored correctly");
239+
}
240+
241+
function _inputParamUsingInConstraints(address account, address caller) internal {
242+
vm.startPrank(ENTRYPOINT_V07_ADDRESS);
243+
244+
// Prepare invalid input param - call should revert (param value below lowerBound)
245+
InputParam[] memory invalidInputParamsA = new InputParam[](1);
246+
invalidInputParamsA[0] = InputParam({
247+
fetcherType: InputParamFetcherType.RAW_BYTES,
248+
valueType: ParamValueType.UINT256,
249+
paramData: abi.encode(40),
250+
constraints: abi.encodePacked(CONSTRAINT_TYPE_IN, abi.encode(bytes32(uint256(41)), bytes32(uint256(43)))) // value must be between 41 & 43
251+
});
252+
253+
// Prepare invalid input param - call should revert (param value above upperBound)
254+
InputParam[] memory invalidInputParamsB = new InputParam[](1);
255+
invalidInputParamsB[0] = InputParam({
256+
fetcherType: InputParamFetcherType.RAW_BYTES,
257+
valueType: ParamValueType.UINT256,
258+
paramData: abi.encode(44),
259+
constraints: abi.encodePacked(CONSTRAINT_TYPE_IN, abi.encode(bytes32(uint256(41)), bytes32(uint256(43)))) // value must be between 41 & 43
260+
});
261+
262+
// Prepare valid input param - call should succeed (param value in bounds)
263+
InputParam[] memory validInputParams = new InputParam[](1);
264+
validInputParams[0] = InputParam({
265+
fetcherType: InputParamFetcherType.RAW_BYTES,
266+
valueType: ParamValueType.UINT256,
267+
paramData: abi.encode(42),
268+
constraints: abi.encodePacked(CONSTRAINT_TYPE_IN, abi.encode(bytes32(uint256(41)), bytes32(uint256(43)))) // value must be between 41 & 43
269+
});
270+
271+
OutputParam[] memory outputParams = new OutputParam[](1);
272+
outputParams[0] = OutputParam({
273+
fetcherType: OutputParamFetcherType.STATIC_CALL,
274+
valueType: ParamValueType.UINT256,
275+
paramData: abi.encode(address(dummyContract), abi.encodeWithSelector(DummyContract.A.selector), address(storageContract), SLOT_A)
276+
});
277+
278+
// Call empty function and it should revert because dynamic param value doesnt meet constraints (value below lower bound)
279+
ComposableExecution[] memory failingExecutionsA = new ComposableExecution[](1);
280+
failingExecutionsA[0] = ComposableExecution({
281+
to: address(0), // no function call
282+
value: 0, // no value sent
283+
functionSig: "", // no calldata encoded
284+
inputParams: invalidInputParamsA, // use constrainted input parameter that's going to fail
285+
outputParams: outputParams
286+
});
287+
vm.expectRevert(bytes("IN constraint not met"));
288+
IComposableExecution(address(account)).executeComposable(failingExecutionsA);
289+
290+
// Call empty function and it should revert because dynamic param value doesnt meet constraints (value below lower bound)
291+
ComposableExecution[] memory failingExecutionsB = new ComposableExecution[](1);
292+
failingExecutionsB[0] = ComposableExecution({
293+
to: address(0), // no function call
294+
value: 0, // no value sent
295+
functionSig: "", // no calldata encoded
296+
inputParams: invalidInputParamsB, // use constrainted input parameter that's going to fail
297+
outputParams: outputParams
298+
});
299+
vm.expectRevert(bytes("IN constraint not met"));
300+
IComposableExecution(address(account)).executeComposable(failingExecutionsB);
301+
302+
// Call empty function and it should NOT revert because dynamic param value meets constraints
303+
ComposableExecution[] memory validExecutions = new ComposableExecution[](1);
304+
validExecutions[0] = ComposableExecution({
305+
to: address(0), // no function call
306+
value: 0, // no value sent
307+
functionSig: "", // no calldata encoded
308+
inputParams: validInputParams, // use valid input params
309+
outputParams: outputParams
310+
});
311+
IComposableExecution(address(account)).executeComposable(validExecutions);
312+
313+
// Expect valid composable execution to store result as per given output params
314+
bytes32 namespace = storageContract.getNamespace(address(account), address(caller));
315+
bytes32 storedValueA = storageContract.readStorage(namespace, SLOT_A);
316+
assertEq(uint256(storedValueA), 42, "Function A result not stored correctly");
317+
}
318+
319+
function _inputParamUsingEqConstraints(address account, address caller) internal {
320+
vm.startPrank(ENTRYPOINT_V07_ADDRESS);
321+
322+
// Prepare invalid input param - call should revert
323+
InputParam[] memory invalidInputParams = new InputParam[](1);
324+
invalidInputParams[0] = InputParam({
325+
fetcherType: InputParamFetcherType.RAW_BYTES,
326+
valueType: ParamValueType.UINT256,
327+
paramData: abi.encode(43),
328+
constraints: abi.encodePacked(CONSTRAINT_TYPE_EQ, bytes32(uint256(42))) // value must be exactly 42
329+
});
330+
331+
// Prepare valid input param - call should succeed
332+
InputParam[] memory validInputParams = new InputParam[](1);
333+
validInputParams[0] = InputParam({
334+
fetcherType: InputParamFetcherType.RAW_BYTES,
335+
valueType: ParamValueType.UINT256,
336+
paramData: abi.encode(42),
337+
constraints: abi.encodePacked(CONSTRAINT_TYPE_EQ, bytes32(uint256(42))) // value must be exactly 42
338+
});
339+
340+
OutputParam[] memory outputParams = new OutputParam[](1);
341+
outputParams[0] = OutputParam({
342+
fetcherType: OutputParamFetcherType.STATIC_CALL,
343+
valueType: ParamValueType.UINT256,
344+
paramData: abi.encode(address(dummyContract), abi.encodeWithSelector(DummyContract.A.selector), address(storageContract), SLOT_A)
345+
});
346+
347+
// Call empty function and it should revert because dynamic param value doesnt meet constraints
348+
ComposableExecution[] memory failingExecutions = new ComposableExecution[](1);
349+
failingExecutions[0] = ComposableExecution({
350+
to: address(0), // no function call
351+
value: 0, // no value sent
352+
functionSig: "", // no calldata encoded
353+
inputParams: invalidInputParams, // use constrainted input parameter that's going to fail
354+
outputParams: outputParams
355+
});
356+
vm.expectRevert(bytes("EQ constraint not met."));
357+
IComposableExecution(address(account)).executeComposable(failingExecutions);
358+
359+
// Call empty function and it should NOT revert because dynamic param value meets constraints
360+
ComposableExecution[] memory validExecutions = new ComposableExecution[](1);
361+
validExecutions[0] = ComposableExecution({
362+
to: address(0), // no function call
363+
value: 0, // no value sent
364+
functionSig: "", // no calldata encoded
365+
inputParams: validInputParams, // use valid input params
366+
outputParams: outputParams
367+
});
368+
IComposableExecution(address(account)).executeComposable(validExecutions);
369+
370+
// Expect valid composable execution to store result as per given output params
371+
bytes32 namespace = storageContract.getNamespace(address(account), address(caller));
372+
bytes32 storedValueA = storageContract.readStorage(namespace, SLOT_A);
373+
assertEq(uint256(storedValueA), 42, "Function A result not stored correctly");
374+
}
375+
119376
function _inputStaticCallOutputExecResult(address account, address caller) internal {
120377
vm.startPrank(ENTRYPOINT_V07_ADDRESS);
121378

@@ -152,7 +409,8 @@ contract ComposableExecutionTest is ComposabilityTestBase {
152409
inputParamsB[0] = InputParam({
153410
fetcherType: InputParamFetcherType.STATIC_CALL,
154411
valueType: ParamValueType.UINT256,
155-
paramData: abi.encode(storageContract, abi.encodeCall(Storage.readStorage, (namespace, SLOT_A)))
412+
paramData: abi.encode(storageContract, abi.encodeCall(Storage.readStorage, (namespace, SLOT_A))),
413+
constraints: ""
156414
});
157415

158416
// Prepare return value config for function B
@@ -190,7 +448,8 @@ contract ComposableExecutionTest is ComposabilityTestBase {
190448
inputParams[0] = InputParam({
191449
fetcherType: InputParamFetcherType.RAW_BYTES,
192450
valueType: ParamValueType.UINT256,
193-
paramData: abi.encode(1)
451+
paramData: abi.encode(1),
452+
constraints: ""
194453
});
195454

196455
// Prepare return value config for function B
@@ -262,12 +521,14 @@ contract ComposableExecutionTest is ComposabilityTestBase {
262521
inputParams_execution1[0] = InputParam({
263522
fetcherType: InputParamFetcherType.RAW_BYTES,
264523
valueType: ParamValueType.UINT256,
265-
paramData: abi.encode(input1)
524+
paramData: abi.encode(input1),
525+
constraints: ""
266526
});
267527
inputParams_execution1[1] = InputParam({
268528
fetcherType: InputParamFetcherType.RAW_BYTES,
269529
valueType: ParamValueType.UINT256,
270-
paramData: abi.encode(input2)
530+
paramData: abi.encode(input2),
531+
constraints: ""
271532
});
272533

273534
OutputParam[] memory outputParams_execution1 = new OutputParam[](2);
@@ -289,12 +550,14 @@ contract ComposableExecutionTest is ComposabilityTestBase {
289550
inputParams_execution2[0] = InputParam({
290551
fetcherType: InputParamFetcherType.STATIC_CALL,
291552
valueType: ParamValueType.UINT256,
292-
paramData: abi.encode(storageContract, abi.encodeCall(Storage.readStorage, (namespace, SLOT_A)))
553+
paramData: abi.encode(storageContract, abi.encodeCall(Storage.readStorage, (namespace, SLOT_A))),
554+
constraints: ""
293555
});
294556
inputParams_execution2[1] = InputParam({
295557
fetcherType: InputParamFetcherType.STATIC_CALL,
296558
valueType: ParamValueType.UINT256,
297-
paramData: abi.encode(storageContract, abi.encodeCall(Storage.readStorage, (namespace, SLOT_B)))
559+
paramData: abi.encode(storageContract, abi.encodeCall(Storage.readStorage, (namespace, SLOT_B))),
560+
constraints: ""
298561
});
299562
OutputParam[] memory outputParams_execution2 = new OutputParam[](0);
300563

0 commit comments

Comments
 (0)