Skip to content
Prev Previous commit
Next Next commit
update template to minimize generated code changes
  • Loading branch information
Amxx committed Mar 26, 2024
commit d16a0ed4d223c6a2df7ea5215f3490f5cfa9878d
147 changes: 74 additions & 73 deletions contracts/utils/Arrays.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ library Arrays {
using StorageSlot for bytes32;

/**
* @dev Sort an array of address (in memory) following the provided comparator function.
* @dev Sort an array of bytes32 (in memory) following the provided comparator function.
*
* This function does the sorting "in place", meaning that it overrides the input. The object is returned for
* convenience, but that returned value can be discarded safely if the caller has a memory pointer to the array.
Expand All @@ -25,23 +25,23 @@ library Arrays {
* consume more gas than is available in a block, leading to potential DoS.
*/
function sort(
address[] memory array,
function(address, address) pure returns (bool) comp
) internal pure returns (address[] memory) {
sort(_castToBytes32Array(array), _castToBytes32Comp(comp));
bytes32[] memory array,
function(bytes32, bytes32) pure returns (bool) comp
) internal pure returns (bytes32[] memory) {
_quickSort(_begin(array), _end(array), comp);
return array;
}

/**
* @dev Variant of {sort} that sorts an array of address in increasing order.
* @dev Variant of {sort} that sorts an array of bytes32 in increasing order.
*/
function sort(address[] memory array) internal pure returns (address[] memory) {
sort(_castToBytes32Array(array), _defaultComp);
function sort(bytes32[] memory array) internal pure returns (bytes32[] memory) {
sort(array, _defaultComp);
return array;
}

/**
* @dev Sort an array of bytes32 (in memory) following the provided comparator function.
* @dev Sort an array of address (in memory) following the provided comparator function.
*
* This function does the sorting "in place", meaning that it overrides the input. The object is returned for
* convenience, but that returned value can be discarded safely if the caller has a memory pointer to the array.
Expand All @@ -52,18 +52,19 @@ library Arrays {
* consume more gas than is available in a block, leading to potential DoS.
*/
function sort(
bytes32[] memory array,
function(bytes32, bytes32) pure returns (bool) comp
) internal pure returns (bytes32[] memory) {
_quickSort(_begin(array), _end(array), comp);
address[] memory array,
function(address, address) pure returns (bool) comp
) internal pure returns (address[] memory) {
sort(_castToBytes32Array(array), _castToBytes32Comp(comp));
return array;
}

/**
* @dev Variant of {sort} that sorts an array of bytes32 in increasing order.
* @dev Variant of {sort} that sorts an array of address in increasing order.
*/
function sort(bytes32[] memory array) internal pure returns (bytes32[] memory) {
return sort(array, _defaultComp);
function sort(address[] memory array) internal pure returns (address[] memory) {
sort(_castToBytes32Array(array), _defaultComp);
return array;
}

/**
Expand Down Expand Up @@ -93,63 +94,6 @@ library Arrays {
return array;
}

/// @dev Comparator for sorting arrays in increasing order.
function _defaultComp(bytes32 a, bytes32 b) private pure returns (bool) {
return a < b;
}

/// @dev Helper: low level cast address memory array to uint256 memory array
function _castToBytes32Array(address[] memory input) private pure returns (bytes32[] memory output) {
assembly {
output := input
}
}

/// @dev Helper: low level cast address comp function to bytes32 comp function
function _castToBytes32Comp(
function(address, address) pure returns (bool) input
) private pure returns (function(bytes32, bytes32) pure returns (bool) output) {
assembly {
output := input
}
}

/// @dev Helper: low level cast uint256 memory array to uint256 memory array
function _castToBytes32Array(uint256[] memory input) private pure returns (bytes32[] memory output) {
assembly {
output := input
}
}

/// @dev Helper: low level cast uint256 comp function to bytes32 comp function
function _castToBytes32Comp(
function(uint256, uint256) pure returns (bool) input
) private pure returns (function(bytes32, bytes32) pure returns (bool) output) {
assembly {
output := input
}
}

/**
* @dev Pointer to the memory location of the first element of `array`.
*/
function _begin(bytes32[] memory array) private pure returns (uint256 ptr) {
/// @solidity memory-safe-assembly
assembly {
ptr := add(array, 0x20)
}
}

/**
* @dev Pointer to the memory location of the first memory word (32bytes) after `array`. This is the memory word
* that comes just after the last element of the array.
*/
function _end(bytes32[] memory array) private pure returns (uint256 ptr) {
unchecked {
return _begin(array) + array.length * 0x20;
}
}

/**
* @dev Performs a quick sort of a segment of memory. The segment sorted starts at `begin` (inclusive), and stops
* at end (exclusive). Sorting follows the `comp` comparator.
Expand Down Expand Up @@ -183,6 +127,26 @@ library Arrays {
}
}

/**
* @dev Pointer to the memory location of the first element of `array`.
*/
function _begin(bytes32[] memory array) private pure returns (uint256 ptr) {
/// @solidity memory-safe-assembly
assembly {
ptr := add(array, 0x20)
}
}

/**
* @dev Pointer to the memory location of the first memory word (32bytes) after `array`. This is the memory word
* that comes just after the last element of the array.
*/
function _end(bytes32[] memory array) private pure returns (uint256 ptr) {
unchecked {
return _begin(array) + array.length * 0x20;
}
}

/**
* @dev Load memory word (as a bytes32) at location `ptr`.
*/
Expand All @@ -204,6 +168,43 @@ library Arrays {
}
}

/// @dev Comparator for sorting arrays in increasing order.
function _defaultComp(bytes32 a, bytes32 b) private pure returns (bool) {
return a < b;
}

/// @dev Helper: low level cast address memory array to uint256 memory array
function _castToBytes32Array(address[] memory input) private pure returns (bytes32[] memory output) {
assembly {
output := input
}
}

/// @dev Helper: low level cast address comp function to bytes32 comp function
function _castToBytes32Comp(
function(address, address) pure returns (bool) input
) private pure returns (function(bytes32, bytes32) pure returns (bool) output) {
assembly {
output := input
}
}

/// @dev Helper: low level cast uint256 memory array to uint256 memory array
function _castToBytes32Array(uint256[] memory input) private pure returns (bytes32[] memory output) {
assembly {
output := input
}
}

/// @dev Helper: low level cast uint256 comp function to bytes32 comp function
function _castToBytes32Comp(
function(uint256, uint256) pure returns (bool) input
) private pure returns (function(bytes32, bytes32) pure returns (bool) output) {
assembly {
output := input
}
}

/**
* @dev Searches a sorted `array` and returns the first index that contains
* a value greater or equal to `element`. If no such index exists (i.e. all
Expand Down
134 changes: 64 additions & 70 deletions scripts/generate/templates/Arrays.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,7 @@ import {Math} from "./math/Math.sol";
*/
`;

const sort = type => {
const sortLine =
type === 'bytes32'
? '_quickSort(_begin(array), _end(array), comp);'
: 'sort(_castToBytes32Array(array), _castToBytes32Comp(comp));';
const ascendingSortLine =
type === 'bytes32'
? 'return sort(array, _defaultComp);'
: 'sort(_castToBytes32Array(array), _defaultComp);\nreturn array;';

return `
const sort = type => `\
/**
* @dev Sort an array of ${type} (in memory) following the provided comparator function.
*
Expand All @@ -39,65 +29,21 @@ const sort = type => {
${type}[] memory array,
function(${type}, ${type}) pure returns (bool) comp
) internal pure returns (${type}[] memory) {
${sortLine}
${
type === 'bytes32'
? '_quickSort(_begin(array), _end(array), comp);'
: 'sort(_castToBytes32Array(array), _castToBytes32Comp(comp));'
}
return array;
}

/**
* @dev Variant of {sort} that sorts an array of ${type} in increasing order.
*/
function sort(${type}[] memory array) internal pure returns (${type}[] memory) {
${ascendingSortLine}
}
`;
};

const defaultComparator = `
/// @dev Comparator for sorting arrays in increasing order.
function _defaultComp(bytes32 a, bytes32 b) private pure returns (bool) {
return a < b;
}
`;

const sortHelpers = type => {
if (type === 'bytes32') return '';
return `/// @dev Helper: low level cast ${type} memory array to uint256 memory array
function _castToBytes32Array(${type}[] memory input) private pure returns (bytes32[] memory output) {
assembly {
output := input
}
}

/// @dev Helper: low level cast ${type} comp function to bytes32 comp function
function _castToBytes32Comp(
function(${type}, ${type}) pure returns (bool) input
) private pure returns (function(bytes32, bytes32) pure returns (bool) output) {
assembly {
output := input
}
}`;
};

const pointers = `
/**
* @dev Pointer to the memory location of the first element of \`array\`.
*/
function _begin(bytes32[] memory array) private pure returns (uint256 ptr) {
/// @solidity memory-safe-assembly
assembly {
ptr := add(array, 0x20)
}
}

/**
* @dev Pointer to the memory location of the first memory word (32bytes) after \`array\`. This is the memory word
* that comes just after the last element of the array.
*/
function _end(bytes32[] memory array) private pure returns (uint256 ptr) {
unchecked {
return _begin(array) + array.length * 0x20;
${type === 'bytes32' ? 'sort(array, _defaultComp);' : 'sort(_castToBytes32Array(array), _defaultComp);'}
return array;
}
}
`;

const quickSort = `
Expand Down Expand Up @@ -134,6 +80,26 @@ function _quickSort(uint256 begin, uint256 end, function(bytes32, bytes32) pure
}
}

/**
* @dev Pointer to the memory location of the first element of \`array\`.
*/
function _begin(bytes32[] memory array) private pure returns (uint256 ptr) {
/// @solidity memory-safe-assembly
assembly {
ptr := add(array, 0x20)
}
}

/**
* @dev Pointer to the memory location of the first memory word (32bytes) after \`array\`. This is the memory word
* that comes just after the last element of the array.
*/
function _end(bytes32[] memory array) private pure returns (uint256 ptr) {
unchecked {
return _begin(array) + array.length * 0x20;
}
}

/**
* @dev Load memory word (as a bytes32) at location \`ptr\`.
*/
Expand All @@ -156,6 +122,31 @@ function _swap(uint256 ptr1, uint256 ptr2) private pure {
}
`;

const defaultComparator = `
/// @dev Comparator for sorting arrays in increasing order.
function _defaultComp(bytes32 a, bytes32 b) private pure returns (bool) {
return a < b;
}
`;

const casting = type => `\
/// @dev Helper: low level cast ${type} memory array to uint256 memory array
function _castToBytes32Array(${type}[] memory input) private pure returns (bytes32[] memory output) {
assembly {
output := input
}
}

/// @dev Helper: low level cast ${type} comp function to bytes32 comp function
function _castToBytes32Comp(
function(${type}, ${type}) pure returns (bool) input
) private pure returns (function(bytes32, bytes32) pure returns (bool) output) {
assembly {
output := input
}
}
`;

const search = `
/**
* @dev Searches a sorted \`array\` and returns the first index that contains
Expand Down Expand Up @@ -376,14 +367,17 @@ module.exports = format(
header.trimEnd(),
'library Arrays {',
'using StorageSlot for bytes32;',
...TYPES.map(sort),
defaultComparator,
...TYPES.map(sortHelpers),
pointers,
// sorting, comparator, helpers and internal
sort('bytes32'),
TYPES.filter(type => type !== 'bytes32').map(sort),
quickSort,
defaultComparator,
TYPES.filter(type => type !== 'bytes32').map(casting),
// lookup
search,
...TYPES.map(unsafeAccessStorage),
...TYPES.map(unsafeAccessMemory),
...TYPES.map(unsafeSetLength),
// unsafe (direct) storage and memory access
TYPES.map(unsafeAccessStorage),
TYPES.map(unsafeAccessMemory),
TYPES.map(unsafeSetLength),
'}',
);