Skip to content

Commit cb233b9

Browse files
committed
Merge branch 'master' into feature/low-level-call
2 parents b547cf5 + 2ea54a1 commit cb233b9

File tree

13 files changed

+483
-41
lines changed

13 files changed

+483
-41
lines changed

.changeset/major-feet-write.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'openzeppelin-solidity': minor
3+
---
4+
5+
`Bytes`: Add `reverseBytes32`, `reverseBytes16`, `reverseBytes8`, `reverseBytes4`, and `reverseBytes2` functions to reverse byte order for converting between little-endian and big-endian representations.

.changeset/violet-turtles-like.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'openzeppelin-solidity': minor
3+
---
4+
5+
`ECDSA`: Add `recoverCalldata` and `tryRecoverCalldata`, variants of `recover` and `tryRecover` that are more efficient when signatures are in calldata.

.changeset/whole-plums-speak.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'openzeppelin-solidity': minor
3+
---
4+
5+
`SignatureChecker`: Add `isValidSignatureNowCalldata(address,bytes32,bytes calldata)` for efficient processing of calldata signatures.

contracts/access/manager/IAccessManager.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ interface IAccessManager {
9797
* previously set delay (not zero), then the function should return false and the caller should schedule the operation
9898
* for future execution.
9999
*
100-
* If `immediate` is true, the delay can be disregarded and the operation can be immediately executed, otherwise
100+
* If `allowed` is true, the delay can be disregarded and the operation can be immediately executed, otherwise
101101
* the operation can be executed if and only if delay is greater than 0.
102102
*
103103
* NOTE: The IAuthority interface does not include the `uint32` delay. This is an extension of that interface that

contracts/token/ERC20/utils/SafeERC20.sol

Lines changed: 112 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -34,33 +34,33 @@ library SafeERC20 {
3434
* non-reverting calls are assumed to be successful.
3535
*/
3636
function safeTransfer(IERC20 token, address to, uint256 value) internal {
37-
Memory.Pointer ptr = Memory.getFreeMemoryPointer();
38-
_callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
39-
Memory.setFreeMemoryPointer(ptr);
37+
if (!_safeTransfer(token, to, value, true)) {
38+
revert SafeERC20FailedOperation(address(token));
39+
}
4040
}
4141

4242
/**
4343
* @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
4444
* calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
4545
*/
4646
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
47-
Memory.Pointer ptr = Memory.getFreeMemoryPointer();
48-
_callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
49-
Memory.setFreeMemoryPointer(ptr);
47+
if (!_safeTransferFrom(token, from, to, value, true)) {
48+
revert SafeERC20FailedOperation(address(token));
49+
}
5050
}
5151

5252
/**
5353
* @dev Variant of {safeTransfer} that returns a bool instead of reverting if the operation is not successful.
5454
*/
5555
function trySafeTransfer(IERC20 token, address to, uint256 value) internal returns (bool) {
56-
return _callOptionalReturnBool(token, abi.encodeCall(token.transfer, (to, value)));
56+
return _safeTransfer(token, to, value, false);
5757
}
5858

5959
/**
6060
* @dev Variant of {safeTransferFrom} that returns a bool instead of reverting if the operation is not successful.
6161
*/
6262
function trySafeTransferFrom(IERC20 token, address from, address to, uint256 value) internal returns (bool) {
63-
return _callOptionalReturnBool(token, abi.encodeCall(token.transferFrom, (from, to, value)));
63+
return _safeTransferFrom(token, from, to, value, false);
6464
}
6565

6666
/**
@@ -106,11 +106,9 @@ library SafeERC20 {
106106
* set here.
107107
*/
108108
function forceApprove(IERC20 token, address spender, uint256 value) internal {
109-
Memory.Pointer ptr = Memory.getFreeMemoryPointer();
110-
bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));
111-
if (!_callOptionalReturnBool(token, approvalCall)) {
112-
_callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
113-
_callOptionalReturn(token, approvalCall);
109+
if (!_safeApprove(token, spender, value, false)) {
110+
if (!_safeApprove(token, spender, 0, true)) revert SafeERC20FailedOperation(address(token));
111+
if (!_safeApprove(token, spender, value, true)) revert SafeERC20FailedOperation(address(token));
114112
}
115113
Memory.setFreeMemoryPointer(ptr);
116114
}
@@ -171,35 +169,116 @@ library SafeERC20 {
171169
}
172170

173171
/**
174-
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
175-
* on the return value: the return value is optional (but if data is returned, it must not be false).
176-
* @param token The token targeted by the call.
177-
* @param data The call data (encoded using abi.encode or one of its variants).
172+
* @dev Imitates a Solidity `token.transfer(to, value)` call, relaxing the requirement on the return value: the
173+
* return value is optional (but if data is returned, it must not be false).
178174
*
179-
* This is a variant of {_callOptionalReturnBool} that reverts if call fails to meet the requirements.
175+
* @param token The token targeted by the call.
176+
* @param to The recipient of the tokens
177+
* @param value The amount of token to transfer
178+
* @param bubble Behavior switch if the transfer call reverts: bubble the revert reason or return a false boolean.
180179
*/
181-
function _callOptionalReturn(IERC20 token, bytes memory data) private {
182-
(bool success, bytes32 returnValue, ) = LowLevelCall.callReturn64Bytes(address(token), data);
180+
function _safeTransfer(IERC20 token, address to, uint256 value, bool bubble) private returns (bool success) {
181+
bytes4 selector = IERC20.transfer.selector;
183182

184-
if (!success) {
185-
LowLevelCall.bubbleRevert();
186-
} else if (LowLevelCall.returnDataSize() == 0 ? address(token).code.length == 0 : uint256(returnValue) != 1) {
187-
revert SafeERC20FailedOperation(address(token));
183+
assembly ("memory-safe") {
184+
let fmp := mload(0x40)
185+
mstore(0x00, selector)
186+
mstore(0x04, and(to, shr(96, not(0))))
187+
mstore(0x24, value)
188+
success := call(gas(), token, 0, 0, 0x44, 0, 0x20)
189+
// if call success and return is true, all is good.
190+
// otherwise (not success or return is not true), we need to perform further checks
191+
if iszero(and(success, eq(mload(0x00), 1))) {
192+
// if the call was a failure and bubble is enabled, bubble the error
193+
if and(iszero(success), bubble) {
194+
returndatacopy(fmp, 0, returndatasize())
195+
revert(fmp, returndatasize())
196+
}
197+
// if the return value is not true, then the call is only successful if:
198+
// - the token address has code
199+
// - the returndata is empty
200+
success := and(success, and(iszero(returndatasize()), gt(extcodesize(token), 0)))
201+
}
202+
mstore(0x40, fmp)
188203
}
189204
}
190205

191206
/**
192-
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
193-
* on the return value: the return value is optional (but if data is returned, it must not be false).
207+
* @dev Imitates a Solidity `token.transferFrom(from, to, value)` call, relaxing the requirement on the return
208+
* value: the return value is optional (but if data is returned, it must not be false).
209+
*
194210
* @param token The token targeted by the call.
195-
* @param data The call data (encoded using abi.encode or one of its variants).
211+
* @param from The sender of the tokens
212+
* @param to The recipient of the tokens
213+
* @param value The amount of token to transfer
214+
* @param bubble Behavior switch if the transfer call reverts: bubble the revert reason or return a false boolean.
215+
*/
216+
function _safeTransferFrom(
217+
IERC20 token,
218+
address from,
219+
address to,
220+
uint256 value,
221+
bool bubble
222+
) private returns (bool success) {
223+
bytes4 selector = IERC20.transferFrom.selector;
224+
225+
assembly ("memory-safe") {
226+
let fmp := mload(0x40)
227+
mstore(0x00, selector)
228+
mstore(0x04, and(from, shr(96, not(0))))
229+
mstore(0x24, and(to, shr(96, not(0))))
230+
mstore(0x44, value)
231+
success := call(gas(), token, 0, 0, 0x64, 0, 0x20)
232+
// if call success and return is true, all is good.
233+
// otherwise (not success or return is not true), we need to perform further checks
234+
if iszero(and(success, eq(mload(0x00), 1))) {
235+
// if the call was a failure and bubble is enabled, bubble the error
236+
if and(iszero(success), bubble) {
237+
returndatacopy(fmp, 0, returndatasize())
238+
revert(fmp, returndatasize())
239+
}
240+
// if the return value is not true, then the call is only successful if:
241+
// - the token address has code
242+
// - the returndata is empty
243+
success := and(success, and(iszero(returndatasize()), gt(extcodesize(token), 0)))
244+
}
245+
mstore(0x40, fmp)
246+
mstore(0x60, 0)
247+
}
248+
}
249+
250+
/**
251+
* @dev Imitates a Solidity `token.approve(spender, value)` call, relaxing the requirement on the return value:
252+
* the return value is optional (but if data is returned, it must not be false).
196253
*
197-
* This is a variant of {_callOptionalReturn} that silently catches all reverts and returns a bool instead.
254+
* @param token The token targeted by the call.
255+
* @param spender The spender of the tokens
256+
* @param value The amount of token to transfer
257+
* @param bubble Behavior switch if the transfer call reverts: bubble the revert reason or return a false boolean.
198258
*/
199-
function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
200-
(bool success, bytes32 returnValue, ) = LowLevelCall.callReturn64Bytes(address(token), data);
201-
return
202-
success &&
203-
(LowLevelCall.returnDataSize() == 0 ? address(token).code.length > 0 : uint256(returnValue) == 1);
259+
function _safeApprove(IERC20 token, address spender, uint256 value, bool bubble) private returns (bool success) {
260+
bytes4 selector = IERC20.approve.selector;
261+
262+
assembly ("memory-safe") {
263+
let fmp := mload(0x40)
264+
mstore(0x00, selector)
265+
mstore(0x04, and(spender, shr(96, not(0))))
266+
mstore(0x24, value)
267+
success := call(gas(), token, 0, 0, 0x44, 0, 0x20)
268+
// if call success and return is true, all is good.
269+
// otherwise (not success or return is not true), we need to perform further checks
270+
if iszero(and(success, eq(mload(0x00), 1))) {
271+
// if the call was a failure and bubble is enabled, bubble the error
272+
if and(iszero(success), bubble) {
273+
returndatacopy(fmp, 0, returndatasize())
274+
revert(fmp, returndatasize())
275+
}
276+
// if the return value is not true, then the call is only successful if:
277+
// - the token address has code
278+
// - the returndata is empty
279+
success := and(success, and(iszero(returndatasize()), gt(extcodesize(token), 0)))
280+
}
281+
mstore(0x40, fmp)
282+
}
204283
}
205284
}

contracts/utils/Bytes.sol

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,58 @@ library Bytes {
9999
return result;
100100
}
101101

102+
/**
103+
* @dev Reverses the byte order of a bytes32 value, converting between little-endian and big-endian.
104+
* Inspired in https://graphics.stanford.edu/~seander/bithacks.html#ReverseParallel[Reverse Parallel]
105+
*/
106+
function reverseBytes32(bytes32 value) internal pure returns (bytes32) {
107+
value = // swap bytes
108+
((value >> 8) & 0x00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF) |
109+
((value & 0x00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF) << 8);
110+
value = // swap 2-byte long pairs
111+
((value >> 16) & 0x0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF) |
112+
((value & 0x0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF) << 16);
113+
value = // swap 4-byte long pairs
114+
((value >> 32) & 0x00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF) |
115+
((value & 0x00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF) << 32);
116+
value = // swap 8-byte long pairs
117+
((value >> 64) & 0x0000000000000000FFFFFFFFFFFFFFFF0000000000000000FFFFFFFFFFFFFFFF) |
118+
((value & 0x0000000000000000FFFFFFFFFFFFFFFF0000000000000000FFFFFFFFFFFFFFFF) << 64);
119+
return (value >> 128) | (value << 128); // swap 16-byte long pairs
120+
}
121+
122+
/// @dev Same as {reverseBytes32} but optimized for 128-bit values.
123+
function reverseBytes16(bytes16 value) internal pure returns (bytes16) {
124+
value = // swap bytes
125+
((value & 0xFF00FF00FF00FF00FF00FF00FF00FF00) >> 8) |
126+
((value & 0x00FF00FF00FF00FF00FF00FF00FF00FF) << 8);
127+
value = // swap 2-byte long pairs
128+
((value & 0xFFFF0000FFFF0000FFFF0000FFFF0000) >> 16) |
129+
((value & 0x0000FFFF0000FFFF0000FFFF0000FFFF) << 16);
130+
value = // swap 4-byte long pairs
131+
((value & 0xFFFFFFFF00000000FFFFFFFF00000000) >> 32) |
132+
((value & 0x00000000FFFFFFFF00000000FFFFFFFF) << 32);
133+
return (value >> 64) | (value << 64); // swap 8-byte long pairs
134+
}
135+
136+
/// @dev Same as {reverseBytes32} but optimized for 64-bit values.
137+
function reverseBytes8(bytes8 value) internal pure returns (bytes8) {
138+
value = ((value & 0xFF00FF00FF00FF00) >> 8) | ((value & 0x00FF00FF00FF00FF) << 8); // swap bytes
139+
value = ((value & 0xFFFF0000FFFF0000) >> 16) | ((value & 0x0000FFFF0000FFFF) << 16); // swap 2-byte long pairs
140+
return (value >> 32) | (value << 32); // swap 4-byte long pairs
141+
}
142+
143+
/// @dev Same as {reverseBytes32} but optimized for 32-bit values.
144+
function reverseBytes4(bytes4 value) internal pure returns (bytes4) {
145+
value = ((value & 0xFF00FF00) >> 8) | ((value & 0x00FF00FF) << 8); // swap bytes
146+
return (value >> 16) | (value << 16); // swap 2-byte long pairs
147+
}
148+
149+
/// @dev Same as {reverseBytes32} but optimized for 16-bit values.
150+
function reverseBytes2(bytes2 value) internal pure returns (bytes2) {
151+
return (value >> 8) | (value << 8);
152+
}
153+
102154
/**
103155
* @dev Reads a bytes32 from a bytes array without bounds checking.
104156
*

contracts/utils/cryptography/ECDSA.sol

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,30 @@ library ECDSA {
7474
}
7575
}
7676

77+
/**
78+
* @dev Variant of {tryRecover} that takes a signature in calldata
79+
*/
80+
function tryRecoverCalldata(
81+
bytes32 hash,
82+
bytes calldata signature
83+
) internal pure returns (address recovered, RecoverError err, bytes32 errArg) {
84+
if (signature.length == 65) {
85+
bytes32 r;
86+
bytes32 s;
87+
uint8 v;
88+
// ecrecover takes the signature parameters, calldata slices would work here, but are
89+
// significantly more expensive (length check) than using calldataload in assembly.
90+
assembly ("memory-safe") {
91+
r := calldataload(signature.offset)
92+
s := calldataload(add(signature.offset, 0x20))
93+
v := byte(0, calldataload(add(signature.offset, 0x40)))
94+
}
95+
return tryRecover(hash, v, r, s);
96+
} else {
97+
return (address(0), RecoverError.InvalidSignatureLength, bytes32(signature.length));
98+
}
99+
}
100+
77101
/**
78102
* @dev Returns the address that signed a hashed message (`hash`) with
79103
* `signature`. This address can then be used for verification purposes.
@@ -94,6 +118,15 @@ library ECDSA {
94118
return recovered;
95119
}
96120

121+
/**
122+
* @dev Variant of {recover} that takes a signature in calldata
123+
*/
124+
function recoverCalldata(bytes32 hash, bytes calldata signature) internal pure returns (address) {
125+
(address recovered, RecoverError error, bytes32 errorArg) = tryRecoverCalldata(hash, signature);
126+
_throwError(error, errorArg);
127+
return recovered;
128+
}
129+
97130
/**
98131
* @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
99132
*

contracts/utils/cryptography/SignatureChecker.sol

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,22 @@ library SignatureChecker {
4040
}
4141
}
4242

43+
/**
44+
* @dev Variant of {isValidSignatureNow} that takes a signature in calldata
45+
*/
46+
function isValidSignatureNowCalldata(
47+
address signer,
48+
bytes32 hash,
49+
bytes calldata signature
50+
) internal view returns (bool) {
51+
if (signer.code.length == 0) {
52+
(address recovered, ECDSA.RecoverError err, ) = ECDSA.tryRecoverCalldata(hash, signature);
53+
return err == ECDSA.RecoverError.NoError && recovered == signer;
54+
} else {
55+
return isValidERC1271SignatureNow(signer, hash, signature);
56+
}
57+
}
58+
4359
/**
4460
* @dev Checks if a signature is valid for a given signer and data hash. The signature is validated
4561
* against the signer smart contract using ERC-1271.
@@ -51,13 +67,26 @@ library SignatureChecker {
5167
address signer,
5268
bytes32 hash,
5369
bytes memory signature
54-
) internal view returns (bool) {
55-
Memory.Pointer ptr = Memory.getFreeMemoryPointer();
56-
bytes memory params = abi.encodeCall(IERC1271.isValidSignature, (hash, signature));
57-
(bool success, bytes32 result, ) = LowLevelCall.staticcallReturn64Bytes(signer, params);
58-
Memory.setFreeMemoryPointer(ptr);
70+
) internal view returns (bool result) {
71+
bytes4 selector = IERC1271.isValidSignature.selector;
72+
uint256 length = signature.length;
73+
74+
assembly ("memory-safe") {
75+
// Encoded calldata is :
76+
// [ 0x00 - 0x03 ] <selector>
77+
// [ 0x04 - 0x23 ] <hash>
78+
// [ 0x24 - 0x44 ] <signature offset> (0x40)
79+
// [ 0x44 - 0x64 ] <signature length>
80+
// [ 0x64 - ... ] <signature data>
81+
let ptr := mload(0x40)
82+
mstore(ptr, selector)
83+
mstore(add(ptr, 0x04), hash)
84+
mstore(add(ptr, 0x24), 0x40)
85+
mcopy(add(ptr, 0x44), signature, add(length, 0x20))
5986

60-
return success && LowLevelCall.returnDataSize() >= 32 && result == bytes32(IERC1271.isValidSignature.selector);
87+
let success := staticcall(gas(), signer, ptr, add(length, 0x64), 0, 0x20)
88+
result := and(success, and(gt(returndatasize(), 0x19), eq(mload(0x00), selector)))
89+
}
6190
}
6291

6392
/**

test/helpers/constants.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
module.exports = {
2+
MAX_UINT16: 2n ** 16n - 1n,
23
MAX_UINT32: 2n ** 32n - 1n,
34
MAX_UINT48: 2n ** 48n - 1n,
45
MAX_UINT64: 2n ** 64n - 1n,
6+
MAX_UINT128: 2n ** 128n - 1n,
57
};

0 commit comments

Comments
 (0)