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:
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@ -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));
|
||||
|
||||
Reference in New Issue
Block a user