Skip to content

Commit 17d2dee

Browse files
ernestognwAmxx
andcommitted
Implement feedback for M-01, L-08, L-09 (OpenZeppelin#5324)
Co-authored-by: Hadrien Croubois <hadrien.croubois@gmail.com> Signed-off-by: Hadrien Croubois <hadrien.croubois@gmail.com>
1 parent b196291 commit 17d2dee

File tree

8 files changed

+99
-23
lines changed

8 files changed

+99
-23
lines changed

contracts/account/utils/draft-ERC7579Utils.sol

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,10 @@ library ERC7579Utils {
3838

3939
/**
4040
* @dev Emits when an {EXECTYPE_TRY} execution fails.
41-
* @param batchExecutionIndex The index of the failed transaction in the execution batch.
41+
* @param batchExecutionIndex The index of the failed call in the execution batch.
42+
* @param returndata The returned data from the failed call.
4243
*/
43-
event ERC7579TryExecuteFail(uint256 batchExecutionIndex, bytes result);
44+
event ERC7579TryExecuteFail(uint256 batchExecutionIndex, bytes returndata);
4445

4546
/// @dev The provided {CallType} is not supported.
4647
error ERC7579UnsupportedCallType(CallType callType);

contracts/governance/extensions/GovernorCountingOverridable.sol

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {VotesExtended} from "../utils/VotesExtended.sol";
88
import {GovernorVotes} from "./GovernorVotes.sol";
99

1010
/**
11-
* @dev Extension of {Governor} which enables delegatees to override the vote of their delegates. This module requires a
11+
* @dev Extension of {Governor} which enables delegators to override the vote of their delegates. This module requires a
1212
* token that inherits {VotesExtended}.
1313
*/
1414
abstract contract GovernorCountingOverridable is GovernorVotes {
@@ -162,7 +162,7 @@ abstract contract GovernorCountingOverridable is GovernorVotes {
162162
return overridenWeight;
163163
}
164164

165-
/// @dev Variant of {Governor-_castVote} that deals with vote overrides.
165+
/// @dev Variant of {Governor-_castVote} that deals with vote overrides. Returns the overridden weight.
166166
function _castOverride(
167167
uint256 proposalId,
168168
address account,
@@ -180,7 +180,7 @@ abstract contract GovernorCountingOverridable is GovernorVotes {
180180
return overridenWeight;
181181
}
182182

183-
/// @dev Public function for casting an override vote
183+
/// @dev Public function for casting an override vote. Returns the overridden weight.
184184
function castOverrideVote(
185185
uint256 proposalId,
186186
uint8 support,
@@ -190,7 +190,7 @@ abstract contract GovernorCountingOverridable is GovernorVotes {
190190
return _castOverride(proposalId, voter, support, reason);
191191
}
192192

193-
/// @dev Public function for casting an override vote using a voter's signature
193+
/// @dev Public function for casting an override vote using a voter's signature. Returns the overridden weight.
194194
function castOverrideVoteBySig(
195195
uint256 proposalId,
196196
uint8 support,

contracts/interfaces/draft-IERC4337.sol

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,18 @@ struct PackedUserOperation {
4545

4646
/**
4747
* @dev Aggregates and validates multiple signatures for a batch of user operations.
48+
*
49+
* A contract could implement this interface with custom validation schemes that allow signature aggregation,
50+
* enabling significant optimizations and gas savings for execution and transaction data cost.
51+
*
52+
* Bundlers and clients whitelist supported aggregators.
53+
*
54+
* See https://eips.ethereum.org/EIPS/eip-7766[ERC-7766]
4855
*/
4956
interface IAggregator {
5057
/**
5158
* @dev Validates the signature for a user operation.
59+
* Returns an alternative signature that should be used during bundling.
5260
*/
5361
function validateUserOpSignature(
5462
PackedUserOperation calldata userOp
@@ -73,6 +81,12 @@ interface IAggregator {
7381

7482
/**
7583
* @dev Handle nonce management for accounts.
84+
*
85+
* Nonces are used in accounts as a replay protection mechanism and to ensure the order of user operations.
86+
* To avoid limiting the number of operations an account can perform, the interface allows using parallel
87+
* nonces by using a `key` parameter.
88+
*
89+
* See https://eips.ethereum.org/EIPS/eip-4337#semi-abstracted-nonce-support[ERC-4337 semi-abstracted nonce support].
7690
*/
7791
interface IEntryPointNonces {
7892
/**
@@ -84,7 +98,11 @@ interface IEntryPointNonces {
8498
}
8599

86100
/**
87-
* @dev Handle stake management for accounts.
101+
* @dev Handle stake management for entities (i.e. accounts, paymasters, factories).
102+
*
103+
* The EntryPoint must implement the following API to let entities like paymasters have a stake,
104+
* and thus have more flexibility in their storage access
105+
* (see https://eips.ethereum.org/EIPS/eip-4337#reputation-scoring-and-throttlingbanning-for-global-entities[reputation, throttling and banning.])
88106
*/
89107
interface IEntryPointStake {
90108
/**
@@ -120,6 +138,8 @@ interface IEntryPointStake {
120138

121139
/**
122140
* @dev Entry point for user operations.
141+
*
142+
* User operations are validated and executed by this contract.
123143
*/
124144
interface IEntryPoint is IEntryPointNonces, IEntryPointStake {
125145
/**
@@ -158,11 +178,23 @@ interface IEntryPoint is IEntryPointNonces, IEntryPointStake {
158178
}
159179

160180
/**
161-
* @dev Base interface for an account.
181+
* @dev Base interface for an ERC-4337 account.
162182
*/
163183
interface IAccount {
164184
/**
165185
* @dev Validates a user operation.
186+
*
187+
* * MUST validate the caller is a trusted EntryPoint
188+
* * MUST validate that the signature is a valid signature of the userOpHash, and SHOULD
189+
* return SIG_VALIDATION_FAILED (and not revert) on signature mismatch. Any other error MUST revert.
190+
* * MUST pay the entryPoint (caller) at least the “missingAccountFunds” (which might
191+
* be zero, in case the current account’s deposit is high enough)
192+
*
193+
* Returns an encoded packed validation data that is composed of the following elements:
194+
*
195+
* - `authorizer` (`address`): 0 for success, 1 for failure, otherwise the address of an authorizer contract
196+
* - `validUntil` (`uint48`): The UserOp is valid only up to this time. Zero for “infinite”.
197+
* - `validAfter` (`uint48`): The UserOp is valid only after this time.
166198
*/
167199
function validateUserOp(
168200
PackedUserOperation calldata userOp,
@@ -195,7 +227,8 @@ interface IPaymaster {
195227
}
196228

197229
/**
198-
* @dev Validates whether the paymaster is willing to pay for the user operation.
230+
* @dev Validates whether the paymaster is willing to pay for the user operation. See
231+
* {IAccount-validateUserOp} for additional information on the return value.
199232
*
200233
* NOTE: Bundlers will reject this method if it modifies the state, unless it's whitelisted.
201234
*/
@@ -207,6 +240,8 @@ interface IPaymaster {
207240

208241
/**
209242
* @dev Verifies the sender is the entrypoint.
243+
* @param actualGasCost the actual amount paid (by account or paymaster) for this UserOperation
244+
* @param actualUserOpFeePerGas total gas used by this UserOperation (including preVerification, creation, validation and execution)
210245
*/
211246
function postOp(
212247
PostOpMode mode,

contracts/interfaces/draft-IERC7579.sol

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ interface IERC7579Validator is IERC7579Module {
5050
*
5151
* MUST validate that the signature is a valid signature of the userOpHash
5252
* SHOULD return ERC-4337's SIG_VALIDATION_FAILED (and not revert) on signature mismatch
53-
* See ERC-4337 for additional information on the return value
53+
* See {IAccount-validateUserOp} for additional information on the return value
5454
*/
5555
function validateUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash) external returns (uint256);
5656

@@ -127,6 +127,7 @@ interface IERC7579Execution {
127127
* This function is intended to be called by Executor Modules
128128
* @param mode The encoded execution mode of the transaction. See ModeLib.sol for details
129129
* @param executionCalldata The encoded execution call data
130+
* @return returnData An array with the returned data of each executed subcall
130131
*
131132
* MUST ensure adequate authorization control: i.e. onlyExecutorModule
132133
* If a mode is requested that is not supported by the Account, it MUST revert
@@ -140,7 +141,7 @@ interface IERC7579Execution {
140141
/**
141142
* @dev ERC-7579 Account Config.
142143
*
143-
* Accounts should implement this interface to exposes information that identifies the account, supported modules and capabilities.
144+
* Accounts should implement this interface to expose information that identifies the account, supported modules and capabilities.
144145
*/
145146
interface IERC7579AccountConfig {
146147
/**
@@ -174,7 +175,7 @@ interface IERC7579AccountConfig {
174175
/**
175176
* @dev ERC-7579 Module Config.
176177
*
177-
* Accounts should implement this interface to allows installing and uninstalling modules.
178+
* Accounts should implement this interface to allow installing and uninstalling modules.
178179
*/
179180
interface IERC7579ModuleConfig {
180181
event ModuleInstalled(uint256 moduleTypeId, address module);

contracts/proxy/Clones.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ library Clones {
163163
* access the arguments within the implementation, use {fetchCloneArgs}.
164164
*
165165
* This function uses the create2 opcode and a `salt` to deterministically deploy the clone. Using the same
166-
* `implementation`, `args` and `salt` multiple time will revert, since the clones cannot be deployed twice
166+
* `implementation`, `args` and `salt` multiple times will revert, since the clones cannot be deployed twice
167167
* at the same address.
168168
*/
169169
function cloneDeterministicWithImmutableArgs(

contracts/token/ERC20/extensions/ERC1363.sol

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ abstract contract ERC1363 is ERC20, ERC165, IERC1363 {
4848

4949
/**
5050
* @dev Moves a `value` amount of tokens from the caller's account to `to`
51-
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
51+
* and then calls {IERC1363Receiver-onTransferReceived} on `to`. Returns a flag that indicates
52+
* if the call succeeded.
5253
*
5354
* Requirements:
5455
*
@@ -75,7 +76,8 @@ abstract contract ERC1363 is ERC20, ERC165, IERC1363 {
7576

7677
/**
7778
* @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
78-
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
79+
* and then calls {IERC1363Receiver-onTransferReceived} on `to`. Returns a flag that indicates
80+
* if the call succeeded.
7981
*
8082
* Requirements:
8183
*
@@ -108,6 +110,7 @@ abstract contract ERC1363 is ERC20, ERC165, IERC1363 {
108110
/**
109111
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
110112
* caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
113+
* Returns a flag that indicates if the call succeeded.
111114
*
112115
* Requirements:
113116
*

contracts/utils/Strings.sol

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,8 @@ library Strings {
177177
}
178178

179179
/**
180-
* @dev Variant of {tryParseUint} that does not check bounds and returns (true, 0) if they are invalid.
180+
* @dev Implementation of {tryParseUint} that does not check bounds. Caller should make sure that
181+
* `begin <= end <= input.length`. Other inputs would result in undefined behavior.
181182
*/
182183
function _tryParseUintUncheckedBounds(
183184
string memory input,
@@ -249,7 +250,8 @@ library Strings {
249250
}
250251

251252
/**
252-
* @dev Variant of {tryParseInt} that does not check bounds and returns (true, 0) if they are invalid.
253+
* @dev Implementation of {tryParseInt} that does not check bounds. Caller should make sure that
254+
* `begin <= end <= input.length`. Other inputs would result in undefined behavior.
253255
*/
254256
function _tryParseIntUncheckedBounds(
255257
string memory input,
@@ -323,7 +325,8 @@ library Strings {
323325
}
324326

325327
/**
326-
* @dev Variant of {tryParseHexUint} that does not check bounds and returns (true, 0) if they are invalid.
328+
* @dev Implementation of {tryParseHexUint} that does not check bounds. Caller should make sure that
329+
* `begin <= end <= input.length`. Other inputs would result in undefined behavior.
327330
*/
328331
function _tryParseHexUintUncheckedBounds(
329332
string memory input,
@@ -333,7 +336,7 @@ library Strings {
333336
bytes memory buffer = bytes(input);
334337

335338
// skip 0x prefix if present
336-
bool hasPrefix = (begin < end + 1) && bytes2(_unsafeReadBytesOffset(buffer, begin)) == bytes2("0x"); // don't do out-of-bound (possibly unsafe) read if sub-string is empty
339+
bool hasPrefix = (end > begin + 1) && bytes2(_unsafeReadBytesOffset(buffer, begin)) == bytes2("0x"); // don't do out-of-bound (possibly unsafe) read if sub-string is empty
337340
uint256 offset = hasPrefix.toUint() * 2;
338341

339342
uint256 result = 0;
@@ -390,12 +393,13 @@ library Strings {
390393
uint256 begin,
391394
uint256 end
392395
) internal pure returns (bool success, address value) {
393-
// check that input is the correct length
394-
bool hasPrefix = (begin < end + 1) && bytes2(_unsafeReadBytesOffset(bytes(input), begin)) == bytes2("0x"); // don't do out-of-bound (possibly unsafe) read if sub-string is empty
396+
if (end > bytes(input).length || begin > end) return (false, address(0));
395397

398+
bool hasPrefix = (end > begin + 1) && bytes2(_unsafeReadBytesOffset(bytes(input), begin)) == bytes2("0x"); // don't do out-of-bound (possibly unsafe) read if sub-string is empty
396399
uint256 expectedLength = 40 + hasPrefix.toUint() * 2;
397400

398-
if (end - begin == expectedLength && end <= bytes(input).length) {
401+
// check that input is the correct length
402+
if (end - begin == expectedLength) {
399403
// length guarantees that this does not overflow, and value is at most type(uint160).max
400404
(bool s, uint256 v) = _tryParseHexUintUncheckedBounds(input, begin, end);
401405
return (s, address(uint160(v)));

test/account/utils/draft-ERC4337Utils.test.js

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,14 @@ const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
44

55
const { packValidationData, UserOperation } = require('../../helpers/erc4337');
66
const { MAX_UINT48 } = require('../../helpers/constants');
7+
const ADDRESS_ONE = '0x0000000000000000000000000000000000000001';
78

89
const fixture = async () => {
910
const [authorizer, sender, entrypoint, factory, paymaster] = await ethers.getSigners();
1011
const utils = await ethers.deployContract('$ERC4337Utils');
11-
return { utils, authorizer, sender, entrypoint, factory, paymaster };
12+
const SIG_VALIDATION_SUCCESS = await utils.$SIG_VALIDATION_SUCCESS();
13+
const SIG_VALIDATION_FAILED = await utils.$SIG_VALIDATION_FAILED();
14+
return { utils, authorizer, sender, entrypoint, factory, paymaster, SIG_VALIDATION_SUCCESS, SIG_VALIDATION_FAILED };
1215
};
1316

1417
describe('ERC4337Utils', function () {
@@ -41,6 +44,20 @@ describe('ERC4337Utils', function () {
4144
MAX_UINT48,
4245
]);
4346
});
47+
48+
it('parse canonical values', async function () {
49+
expect(this.utils.$parseValidationData(this.SIG_VALIDATION_SUCCESS)).to.eventually.deep.equal([
50+
ethers.ZeroAddress,
51+
0n,
52+
MAX_UINT48,
53+
]);
54+
55+
expect(this.utils.$parseValidationData(this.SIG_VALIDATION_FAILED)).to.eventually.deep.equal([
56+
ADDRESS_ONE,
57+
0n,
58+
MAX_UINT48,
59+
]);
60+
});
4461
});
4562

4663
describe('packValidationData', function () {
@@ -65,6 +82,21 @@ describe('ERC4337Utils', function () {
6582
validationData,
6683
);
6784
});
85+
86+
it('packing reproduced canonical values', async function () {
87+
expect(this.utils.$packValidationData(ethers.Typed.address(ethers.ZeroAddress), 0n, 0n)).to.eventually.equal(
88+
this.SIG_VALIDATION_SUCCESS,
89+
);
90+
expect(this.utils.$packValidationData(ethers.Typed.bool(true), 0n, 0n)).to.eventually.equal(
91+
this.SIG_VALIDATION_SUCCESS,
92+
);
93+
expect(this.utils.$packValidationData(ethers.Typed.address(ADDRESS_ONE), 0n, 0n)).to.eventually.equal(
94+
this.SIG_VALIDATION_FAILED,
95+
);
96+
expect(this.utils.$packValidationData(ethers.Typed.bool(false), 0n, 0n)).to.eventually.equal(
97+
this.SIG_VALIDATION_FAILED,
98+
);
99+
});
68100
});
69101

70102
describe('combineValidationData', function () {

0 commit comments

Comments
 (0)