Skip to content

Commit

Permalink
Generate already lint code from procedural generation (#5060)
Browse files Browse the repository at this point in the history
  • Loading branch information
Amxx authored May 30, 2024
1 parent a241f09 commit dd1e898
Show file tree
Hide file tree
Showing 11 changed files with 315 additions and 310 deletions.
6 changes: 3 additions & 3 deletions scripts/generate/run.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env node

const cp = require('child_process');
// const cp = require('child_process');
const fs = require('fs');
const path = require('path');
const format = require('./format-lines');
Expand All @@ -23,11 +23,11 @@ function generateFromTemplate(file, template, outputPrefix = '') {
...(version ? [version + ` (${file})`] : []),
`// This file was procedurally generated from ${input}.`,
'',
require(template),
require(template).trimEnd(),
);

fs.writeFileSync(output, content);
cp.execFileSync('prettier', ['--write', output]);
// cp.execFileSync('prettier', ['--write', output]);
}

// Contracts
Expand Down
149 changes: 78 additions & 71 deletions scripts/generate/templates/Arrays.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,39 +15,39 @@ import {Math} from "./math/Math.sol";
`;

const sort = type => `\
/**
* @dev Sort an array of ${type} (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.
*
* NOTE: this function's cost is \`O(n · log(n))\` in average and \`O(n²)\` in the worst case, with n the length of the
* array. Using it in view functions that are executed through \`eth_call\` is safe, but one should be very careful
* when executing this as part of a transaction. If the array being sorted is too large, the sort operation may
* consume more gas than is available in a block, leading to potential DoS.
*/
function sort(
${type}[] memory array,
function(${type}, ${type}) pure returns (bool) comp
) internal pure returns (${type}[] memory) {
${
type === 'bytes32'
? '_quickSort(_begin(array), _end(array), comp);'
: 'sort(_castToBytes32Array(array), _castToBytes32Comp(comp));'
}
return array;
/**
* @dev Sort an array of ${type} (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.
*
* NOTE: this function's cost is \`O(n · log(n))\` in average and \`O(n²)\` in the worst case, with n the length of the
* array. Using it in view functions that are executed through \`eth_call\` is safe, but one should be very careful
* when executing this as part of a transaction. If the array being sorted is too large, the sort operation may
* consume more gas than is available in a block, leading to potential DoS.
*/
function sort(
${type}[] memory array,
function(${type}, ${type}) pure returns (bool) comp
) internal pure returns (${type}[] memory) {
${
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) {
${type === 'bytes32' ? 'sort(array, _defaultComp);' : 'sort(_castToBytes32Array(array), _defaultComp);'}
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) {
${type === 'bytes32' ? 'sort(array, _defaultComp);' : 'sort(_castToBytes32Array(array), _defaultComp);'}
return array;
}
`;

const quickSort = `
const quickSort = `\
/**
* @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 @@ -123,34 +123,34 @@ 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 defaultComparator = `\
/// @dev Comparator for sorting arrays in increasing order.
function _defaultComp(bytes32 a, bytes32 b) private pure returns (bool) {
return a < b;
}
`;

const castArray = 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} memory array to uint256 memory array
function _castToBytes32Array(${type}[] memory input) private pure returns (bytes32[] memory output) {
assembly {
output := input
}
}
`;

const castComparator = type => `\
/// @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
}
/// @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 = `
const search = `\
/**
* @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 Expand Up @@ -319,12 +319,12 @@ function upperBoundMemory(uint256[] memory array, uint256 element) internal pure
}
`;

const unsafeAccessStorage = type => `
const unsafeAccessStorage = type => `\
/**
* @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check.
*
* WARNING: Only use if you are certain \`pos\` is lower than the array length.
*/
* @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check.
*
* WARNING: Only use if you are certain \`pos\` is lower than the array length.
*/
function unsafeAccess(${type}[] storage arr, uint256 pos) internal pure returns (StorageSlot.${capitalize(
type,
)}Slot storage) {
Expand All @@ -334,9 +334,10 @@ function unsafeAccess(${type}[] storage arr, uint256 pos) internal pure returns
slot := arr.slot
}
return slot.deriveArray().offset(pos).get${capitalize(type)}Slot();
}`;
}
`;

const unsafeAccessMemory = type => `
const unsafeAccessMemory = type => `\
/**
* @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check.
*
Expand All @@ -349,7 +350,7 @@ function unsafeMemoryAccess(${type}[] memory arr, uint256 pos) internal pure ret
}
`;

const unsafeSetLength = type => `
const unsafeSetLength = type => `\
/**
* @dev Helper to set the length of an dynamic array. Directly writing to \`.length\` is forbidden.
*
Expand All @@ -360,26 +361,32 @@ function unsafeSetLength(${type}[] storage array, uint256 len) internal {
assembly {
sstore(array.slot, len)
}
}`;
}
`;

// GENERATE
module.exports = format(
header.trimEnd(),
'library Arrays {',
'using SlotDerivation for bytes32;',
'using StorageSlot for bytes32;',
// sorting, comparator, helpers and internal
sort('bytes32'),
TYPES.filter(type => type !== 'bytes32').map(sort),
quickSort,
defaultComparator,
TYPES.filter(type => type !== 'bytes32').map(castArray),
TYPES.filter(type => type !== 'bytes32').map(castComparator),
// lookup
search,
// unsafe (direct) storage and memory access
TYPES.map(unsafeAccessStorage),
TYPES.map(unsafeAccessMemory),
TYPES.map(unsafeSetLength),
format(
[].concat(
'using SlotDerivation for bytes32;',
'using StorageSlot for bytes32;',
'',
// sorting, comparator, helpers and internal
sort('bytes32'),
TYPES.filter(type => type !== 'bytes32').map(sort),
quickSort,
defaultComparator,
TYPES.filter(type => type !== 'bytes32').map(castArray),
TYPES.filter(type => type !== 'bytes32').map(castComparator),
// lookup
search,
// unsafe (direct) storage and memory access
TYPES.map(unsafeAccessStorage),
TYPES.map(unsafeAccessMemory),
TYPES.map(unsafeSetLength),
),
).trimEnd(),
'}',
);
49 changes: 18 additions & 31 deletions scripts/generate/templates/Checkpoints.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ import {Math} from "../math/Math.sol";
`;

const errors = `\
/**
* @dev A value was attempted to be inserted on a past checkpoint.
*/
error CheckpointUnorderedInsertion();
/**
* @dev A value was attempted to be inserted on a past checkpoint.
*/
error CheckpointUnorderedInsertion();
`;

const template = opts => `\
Expand All @@ -37,15 +37,11 @@ struct ${opts.checkpointTypeName} {
* @dev Pushes a (\`key\`, \`value\`) pair into a ${opts.historyTypeName} so that it is stored as the checkpoint.
*
* Returns previous value and new value.
*
*
* IMPORTANT: Never accept \`key\` as a user input, since an arbitrary \`type(${opts.keyTypeName}).max\` key set will disable the
* library.
*/
function push(
${opts.historyTypeName} storage self,
${opts.keyTypeName} key,
${opts.valueTypeName} value
) internal returns (${opts.valueTypeName}, ${opts.valueTypeName}) {
function push(${opts.historyTypeName} storage self, ${opts.keyTypeName} key, ${opts.valueTypeName} value) internal returns (${opts.valueTypeName}, ${opts.valueTypeName}) {
return _insert(self.${opts.checkpointFieldName}, key, value);
}
Expand Down Expand Up @@ -108,15 +104,7 @@ function latest(${opts.historyTypeName} storage self) internal view returns (${o
* @dev Returns whether there is a checkpoint in the structure (i.e. it is not empty), and if so the key and value
* in the most recent checkpoint.
*/
function latestCheckpoint(${opts.historyTypeName} storage self)
internal
view
returns (
bool exists,
${opts.keyTypeName} ${opts.keyFieldName},
${opts.valueTypeName} ${opts.valueFieldName}
)
{
function latestCheckpoint(${opts.historyTypeName} storage self) internal view returns (bool exists, ${opts.keyTypeName} ${opts.keyFieldName}, ${opts.valueTypeName} ${opts.valueFieldName}) {
uint256 pos = self.${opts.checkpointFieldName}.length;
if (pos == 0) {
return (false, 0, 0);
Expand Down Expand Up @@ -144,11 +132,7 @@ function at(${opts.historyTypeName} storage self, uint32 pos) internal view retu
* @dev Pushes a (\`key\`, \`value\`) pair into an ordered list of checkpoints, either by inserting a new checkpoint,
* or by updating the last one.
*/
function _insert(
${opts.checkpointTypeName}[] storage self,
${opts.keyTypeName} key,
${opts.valueTypeName} value
) private returns (${opts.valueTypeName}, ${opts.valueTypeName}) {
function _insert(${opts.checkpointTypeName}[] storage self, ${opts.keyTypeName} key, ${opts.valueTypeName} value) private returns (${opts.valueTypeName}, ${opts.valueTypeName}) {
uint256 pos = self.length;
if (pos > 0) {
Expand Down Expand Up @@ -225,11 +209,10 @@ function _lowerBinaryLookup(
/**
* @dev Access an element of the array without performing bounds check. The position is assumed to be within bounds.
*/
function _unsafeAccess(${opts.checkpointTypeName}[] storage self, uint256 pos)
private
pure
returns (${opts.checkpointTypeName} storage result)
{
function _unsafeAccess(
${opts.checkpointTypeName}[] storage self,
uint256 pos
) private pure returns (${opts.checkpointTypeName} storage result) {
assembly {
mstore(0, self.slot)
result.slot := add(keccak256(0, 0x20), pos)
Expand All @@ -242,7 +225,11 @@ function _unsafeAccess(${opts.checkpointTypeName}[] storage self, uint256 pos)
module.exports = format(
header.trimEnd(),
'library Checkpoints {',
errors,
OPTS.flatMap(opts => template(opts)),
format(
[].concat(
errors,
OPTS.map(opts => template(opts)),
),
).trimEnd(),
'}',
);
Loading

0 comments on commit dd1e898

Please sign in to comment.