Release v5.2 audit fixes (#5330)

Signed-off-by: Hadrien Croubois <hadrien.croubois@gmail.com>
Co-authored-by: Sam Bugs <101145325+0xsambugs@users.noreply.github.com>
Co-authored-by: Ernesto García <ernestognw@gmail.com>
Co-authored-by: Arr00 <13561405+arr00@users.noreply.github.com>
Co-authored-by: wizard <112275929+famouswizard@users.noreply.github.com>
Co-authored-by: leopardracer <136604165+leopardracer@users.noreply.github.com>
Co-authored-by: cairo <cairoeth@protonmail.com>
This commit is contained in:
Hadrien Croubois
2024-12-04 17:37:13 +01:00
committed by GitHub
parent 98d28f9261
commit e5e9ff72f0
26 changed files with 489 additions and 151 deletions

View File

@ -27,15 +27,13 @@ library Bytes {
* NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf[Javascript's `Array.indexOf`]
*/
function indexOf(bytes memory buffer, bytes1 s, uint256 pos) internal pure returns (uint256) {
unchecked {
uint256 length = buffer.length;
for (uint256 i = pos; i < length; ++i) {
if (bytes1(_unsafeReadBytesOffset(buffer, i)) == s) {
return i;
}
uint256 length = buffer.length;
for (uint256 i = pos; i < length; ++i) {
if (bytes1(_unsafeReadBytesOffset(buffer, i)) == s) {
return i;
}
return type(uint256).max;
}
return type(uint256).max;
}
/**

View File

@ -2,10 +2,9 @@
pragma solidity ^0.8.24;
import {SafeCast} from "./math/SafeCast.sol";
import {Bytes} from "./Bytes.sol";
import {CAIP2} from "./CAIP2.sol";
import {Strings} from "./Strings.sol";
import {CAIP2} from "./CAIP2.sol";
/**
* @dev Helper library to format and parse CAIP-10 identifiers
@ -14,9 +13,14 @@ import {Strings} from "./Strings.sol";
* account_id: chain_id + ":" + account_address
* chain_id: [-a-z0-9]{3,8}:[-_a-zA-Z0-9]{1,32} (See {CAIP2})
* account_address: [-.%a-zA-Z0-9]{1,128}
*
* WARNING: According to [CAIP-10's canonicalization section](https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-10.md#canonicalization),
* the implementation remains at the developer's discretion. Please note that case variations may introduce ambiguity.
* For example, when building hashes to identify accounts or data associated to them, multiple representations of the
* same account would derive to different hashes. For EVM chains, we recommend using checksummed addresses for the
* "account_address" part. They can be generated onchain using {Strings-toChecksumHexString}.
*/
library CAIP10 {
using SafeCast for uint256;
using Strings for address;
using Bytes for bytes;

View File

@ -2,7 +2,6 @@
pragma solidity ^0.8.24;
import {SafeCast} from "./math/SafeCast.sol";
import {Bytes} from "./Bytes.sol";
import {Strings} from "./Strings.sol";
@ -13,9 +12,13 @@ import {Strings} from "./Strings.sol";
* chain_id: namespace + ":" + reference
* namespace: [-a-z0-9]{3,8}
* reference: [-_a-zA-Z0-9]{1,32}
*
* WARNING: In some cases, multiple CAIP-2 identifiers may all be valid representation of a single chain.
* For EVM chains, it is recommended to use `eip155:xxx` as the canonical representation (where `xxx` is
* the EIP-155 chain id). Consider the possible ambiguity when processing CAIP-2 identifiers or when using them
* in the context of hashes.
*/
library CAIP2 {
using SafeCast for uint256;
using Strings for uint256;
using Bytes for bytes;

View File

@ -4,22 +4,26 @@ pragma solidity ^0.8.20;
import {Nonces} from "./Nonces.sol";
/**
* @dev Alternative to {Nonces}, that support key-ed nonces.
* @dev Alternative to {Nonces}, that supports key-ed nonces.
*
* Follows the https://eips.ethereum.org/EIPS/eip-4337#semi-abstracted-nonce-support[ERC-4337's semi-abstracted nonce system].
*
* NOTE: This contract inherits from {Nonces} and reuses its storage for the first nonce key (i.e. `0`). This
* makes upgrading from {Nonces} to {NoncesKeyed} safe when using their upgradeable versions (e.g. `NoncesKeyedUpgradeable`).
* Doing so will NOT reset the current state of nonces, avoiding replay attacks where a nonce is reused after the upgrade.
*/
abstract contract NoncesKeyed is Nonces {
mapping(address owner => mapping(uint192 key => uint64)) private _nonces;
/// @dev Returns the next unused nonce for an address and key. Result contains the key prefix.
function nonces(address owner, uint192 key) public view virtual returns (uint256) {
return key == 0 ? nonces(owner) : ((uint256(key) << 64) | _nonces[owner][key]);
return key == 0 ? nonces(owner) : _pack(key, _nonces[owner][key]);
}
/**
* @dev Consumes the next unused nonce for an address and key.
*
* Returns the current value without the key prefix. Consumed nonce is increased, so calling this functions twice
* Returns the current value without the key prefix. Consumed nonce is increased, so calling this function twice
* with the same arguments will return different (sequential) results.
*/
function _useNonce(address owner, uint192 key) internal virtual returns (uint256) {
@ -27,7 +31,7 @@ abstract contract NoncesKeyed is Nonces {
// decremented or reset. This guarantees that the nonce never overflows.
unchecked {
// It is important to do x++ and not ++x here.
return key == 0 ? _useNonce(owner) : _nonces[owner][key]++;
return key == 0 ? _useNonce(owner) : _pack(key, _nonces[owner][key]++);
}
}
@ -35,11 +39,17 @@ abstract contract NoncesKeyed is Nonces {
* @dev Same as {_useNonce} but checking that `nonce` is the next valid for `owner`.
*
* This version takes the key and the nonce in a single uint256 parameter:
* - use the first 8 bytes for the key
* - use the last 24 bytes for the nonce
* - use the first 24 bytes for the key
* - use the last 8 bytes for the nonce
*/
function _useCheckedNonce(address owner, uint256 keyNonce) internal virtual override {
_useCheckedNonce(owner, uint192(keyNonce >> 64), uint64(keyNonce));
(uint192 key, ) = _unpack(keyNonce);
if (key == 0) {
super._useCheckedNonce(owner, keyNonce);
} else {
uint256 current = _useNonce(owner, key);
if (keyNonce != current) revert InvalidAccountNonce(owner, current);
}
}
/**
@ -48,13 +58,16 @@ abstract contract NoncesKeyed is Nonces {
* This version takes the key and the nonce as two different parameters.
*/
function _useCheckedNonce(address owner, uint192 key, uint64 nonce) internal virtual {
if (key == 0) {
super._useCheckedNonce(owner, nonce);
} else {
uint256 current = _useNonce(owner, key);
if (nonce != current) {
revert InvalidAccountNonce(owner, current);
}
}
_useCheckedNonce(owner, _pack(key, nonce));
}
/// @dev Pack key and nonce into a keyNonce
function _pack(uint192 key, uint64 nonce) private pure returns (uint256) {
return (uint256(key) << 64) | nonce;
}
/// @dev Unpack a keyNonce into its key and nonce components
function _unpack(uint256 keyNonce) private pure returns (uint192 key, uint64 nonce) {
return (uint192(keyNonce >> 64), uint64(keyNonce));
}
}

View File

@ -158,7 +158,7 @@ library Strings {
* NOTE: This function will revert if the result does not fit in a `uint256`.
*/
function tryParseUint(string memory input) internal pure returns (bool success, uint256 value) {
return tryParseUint(input, 0, bytes(input).length);
return _tryParseUintUncheckedBounds(input, 0, bytes(input).length);
}
/**
@ -172,6 +172,19 @@ library Strings {
uint256 begin,
uint256 end
) internal pure returns (bool success, uint256 value) {
if (end > bytes(input).length || begin > end) return (false, 0);
return _tryParseUintUncheckedBounds(input, begin, end);
}
/**
* @dev Implementation of {tryParseUint} that does not check bounds. Caller should make sure that
* `begin <= end <= input.length`. Other inputs would result in undefined behavior.
*/
function _tryParseUintUncheckedBounds(
string memory input,
uint256 begin,
uint256 end
) private pure returns (bool success, uint256 value) {
bytes memory buffer = bytes(input);
uint256 result = 0;
@ -216,7 +229,7 @@ library Strings {
* NOTE: This function will revert if the absolute value of the result does not fit in a `uint256`.
*/
function tryParseInt(string memory input) internal pure returns (bool success, int256 value) {
return tryParseInt(input, 0, bytes(input).length);
return _tryParseIntUncheckedBounds(input, 0, bytes(input).length);
}
uint256 private constant ABS_MIN_INT256 = 2 ** 255;
@ -232,10 +245,23 @@ library Strings {
uint256 begin,
uint256 end
) internal pure returns (bool success, int256 value) {
if (end > bytes(input).length || begin > end) return (false, 0);
return _tryParseIntUncheckedBounds(input, begin, end);
}
/**
* @dev Implementation of {tryParseInt} that does not check bounds. Caller should make sure that
* `begin <= end <= input.length`. Other inputs would result in undefined behavior.
*/
function _tryParseIntUncheckedBounds(
string memory input,
uint256 begin,
uint256 end
) private pure returns (bool success, int256 value) {
bytes memory buffer = bytes(input);
// Check presence of a negative sign.
bytes1 sign = bytes1(_unsafeReadBytesOffset(buffer, begin));
bytes1 sign = begin == end ? bytes1(0) : bytes1(_unsafeReadBytesOffset(buffer, begin)); // don't do out-of-bound (possibly unsafe) read if sub-string is empty
bool positiveSign = sign == bytes1("+");
bool negativeSign = sign == bytes1("-");
uint256 offset = (positiveSign || negativeSign).toUint();
@ -280,7 +306,7 @@ library Strings {
* NOTE: This function will revert if the result does not fit in a `uint256`.
*/
function tryParseHexUint(string memory input) internal pure returns (bool success, uint256 value) {
return tryParseHexUint(input, 0, bytes(input).length);
return _tryParseHexUintUncheckedBounds(input, 0, bytes(input).length);
}
/**
@ -294,10 +320,23 @@ library Strings {
uint256 begin,
uint256 end
) internal pure returns (bool success, uint256 value) {
if (end > bytes(input).length || begin > end) return (false, 0);
return _tryParseHexUintUncheckedBounds(input, begin, end);
}
/**
* @dev Implementation of {tryParseHexUint} that does not check bounds. Caller should make sure that
* `begin <= end <= input.length`. Other inputs would result in undefined behavior.
*/
function _tryParseHexUintUncheckedBounds(
string memory input,
uint256 begin,
uint256 end
) private pure returns (bool success, uint256 value) {
bytes memory buffer = bytes(input);
// skip 0x prefix if present
bool hasPrefix = bytes2(_unsafeReadBytesOffset(buffer, begin)) == bytes2("0x");
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
uint256 offset = hasPrefix.toUint() * 2;
uint256 result = 0;
@ -354,13 +393,15 @@ library Strings {
uint256 begin,
uint256 end
) internal pure returns (bool success, address value) {
// check that input is the correct length
bool hasPrefix = bytes2(_unsafeReadBytesOffset(bytes(input), begin)) == bytes2("0x");
if (end > bytes(input).length || begin > end) return (false, address(0));
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
uint256 expectedLength = 40 + hasPrefix.toUint() * 2;
// check that input is the correct length
if (end - begin == expectedLength) {
// length guarantees that this does not overflow, and value is at most type(uint160).max
(bool s, uint256 v) = tryParseHexUint(input, begin, end);
(bool s, uint256 v) = _tryParseHexUintUncheckedBounds(input, begin, end);
return (s, address(uint160(v)));
} else {
return (false, address(0));