Skip to content

Commit 9b5f2e4

Browse files
authored
AA-159 prevent recursive call into handleOps (#257)
* AA-159 prevent recursive call into handleOps * added BeforeExecution event without this event, all events in validation are attributed to the first UserOperation. We can't attribute them to specific UseROp (unless there is exactly one) - but at least not always assign the to the first.
1 parent 19918cd commit 9b5f2e4

File tree

4 files changed

+43
-16
lines changed

4 files changed

+43
-16
lines changed

contracts/core/EntryPoint.sol

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@ import "./StakeManager.sol";
1717
import "./SenderCreator.sol";
1818
import "./Helpers.sol";
1919
import "./NonceManager.sol";
20+
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
2021

21-
contract EntryPoint is IEntryPoint, StakeManager, NonceManager {
22+
contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuard {
2223

2324
using UserOperationLib for UserOperation;
2425

@@ -88,7 +89,7 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager {
8889
* @param ops the operations to execute
8990
* @param beneficiary the address to receive the fees
9091
*/
91-
function handleOps(UserOperation[] calldata ops, address payable beneficiary) public {
92+
function handleOps(UserOperation[] calldata ops, address payable beneficiary) public nonReentrant {
9293

9394
uint256 opslen = ops.length;
9495
UserOpInfo[] memory opInfos = new UserOpInfo[](opslen);
@@ -101,6 +102,7 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager {
101102
}
102103

103104
uint256 collected = 0;
105+
emit BeforeExecution();
104106

105107
for (uint256 i = 0; i < opslen; i++) {
106108
collected += _executeUserOp(i, ops[i], opInfos[i]);
@@ -118,7 +120,7 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager {
118120
function handleAggregatedOps(
119121
UserOpsPerAggregator[] calldata opsPerAggregator,
120122
address payable beneficiary
121-
) public {
123+
) public nonReentrant {
122124

123125
uint256 opasLen = opsPerAggregator.length;
124126
uint256 totalOps = 0;
@@ -143,6 +145,8 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager {
143145

144146
UserOpInfo[] memory opInfos = new UserOpInfo[](totalOps);
145147

148+
emit BeforeExecution();
149+
146150
uint256 opIndex = 0;
147151
for (uint256 a = 0; a < opasLen; a++) {
148152
UserOpsPerAggregator calldata opa = opsPerAggregator[a];

contracts/interfaces/IEntryPoint.sol

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@ interface IEntryPoint is IStakeManager, INonceManager {
4646
*/
4747
event UserOperationRevertReason(bytes32 indexed userOpHash, address indexed sender, uint256 nonce, bytes revertReason);
4848

49+
/**
50+
* an event emitted by handleOps(), before starting the execution loop.
51+
* any event emitted before this event, is part of the validation.
52+
*/
53+
event BeforeExecution();
54+
4955
/**
5056
* signature aggregator used by the following UserOperationEvents within this bundle.
5157
*/

reports/gas-checker.txt

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,28 +12,28 @@
1212
║ │ │ │ (delta for │ (compared to ║
1313
║ │ │ │ one UserOp) │ account.exec()) ║
1414
╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢
15-
║ simple │ 1 │ 78743 │ │ ║
15+
║ simple │ 1 │ 81901 │ │ ║
1616
╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢
17-
║ simple - diff from previous │ 2 │ │ 4416215148
17+
║ simple - diff from previous │ 2 │ │ 4421215198
1818
╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢
19-
║ simple │ 10 │ 476282 │ │ ║
19+
║ simple │ 10 │ 479854 │ │ ║
2020
╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢
21-
║ simple - diff from previous │ 11 │ │ 4417415160
21+
║ simple - diff from previous │ 11 │ │ 4423615222
2222
╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢
23-
║ simple paymaster │ 1 │ 85002 │ │ ║
23+
║ simple paymaster │ 1 │ 88172 │ │ ║
2424
╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢
25-
║ simple paymaster with diff │ 2 │ │ 4313914125
25+
║ simple paymaster with diff │ 2 │ │ 4316514151
2626
╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢
27-
║ simple paymaster │ 10 │ 473554 │ │ ║
27+
║ simple paymaster │ 10 │ 476994 │ │ ║
2828
╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢
29-
║ simple paymaster with diff │ 11 │ │ 4315014136
29+
║ simple paymaster with diff │ 11 │ │ 4326014246
3030
╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢
31-
║ big tx 5k │ 1 │ 179788 │ │ ║
31+
║ big tx 5k │ 1 │ 182958 │ │ ║
3232
╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢
33-
║ big tx - diff from previous │ 2 │ │ 14467319413
33+
║ big tx - diff from previous │ 2 │ │ 14472319463
3434
╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢
35-
║ big tx 5k │ 10 │ 1481914 │ │ ║
35+
║ big tx 5k │ 10 │ 1485438 │ │ ║
3636
╟────────────────────────────────┼───────┼───────────────┼────────────────┼─────────────────────╢
37-
║ big tx - diff from previous │ 11 │ │ 14469819438
37+
║ big tx - diff from previous │ 11 │ │ 14471219452
3838
╚════════════════════════════════╧═══════╧═══════════════╧════════════════╧═════════════════════╝
3939

test/entrypoint.test.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ import {
4242
simulationResultCatch,
4343
createAccount,
4444
getAggregatedAccountInitCode,
45-
simulationResultWithAggregationCatch
45+
simulationResultWithAggregationCatch, decodeRevertReason
4646
} from './testutils'
4747
import { DefaultsForUserOp, fillAndSign, getUserOpHash } from './UserOp'
4848
import { UserOperation } from './UserOperation'
@@ -783,6 +783,23 @@ describe('EntryPoint', function () {
783783
await calcGasUsage(rcpt, entryPoint, beneficiaryAddress)
784784
})
785785

786+
it('should fail to call recursively into handleOps', async () => {
787+
const beneficiaryAddress = createAddress()
788+
789+
const callHandleOps = entryPoint.interface.encodeFunctionData('handleOps', [[], beneficiaryAddress])
790+
const execHandlePost = account.interface.encodeFunctionData('execute', [entryPoint.address, 0, callHandleOps])
791+
const op = await fillAndSign({
792+
sender: account.address,
793+
callData: execHandlePost
794+
}, accountOwner, entryPoint)
795+
796+
const rcpt = await entryPoint.handleOps([op], beneficiaryAddress, {
797+
gasLimit: 1e7
798+
}).then(async r => r.wait())
799+
800+
const error = rcpt.events?.find(ev => ev.event === 'UserOperationRevertReason')
801+
expect(decodeRevertReason(error?.args?.revertReason)).to.eql('Error(ReentrancyGuard: reentrant call)', 'execution of handleOps inside a UserOp should revert')
802+
})
786803
it('should report failure on insufficient verificationGas after creation', async () => {
787804
const op0 = await fillAndSign({
788805
sender: account.address,

0 commit comments

Comments
 (0)