Generate already lint code from procedural generation (#5060)

This commit is contained in:
Hadrien Croubois
2024-05-30 17:16:12 +02:00
committed by GitHub
parent a241f09905
commit dd1e8988ab
11 changed files with 309 additions and 304 deletions

View File

@ -1,6 +1,6 @@
#!/usr/bin/env node #!/usr/bin/env node
const cp = require('child_process'); // const cp = require('child_process');
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
const format = require('./format-lines'); const format = require('./format-lines');
@ -23,11 +23,11 @@ function generateFromTemplate(file, template, outputPrefix = '') {
...(version ? [version + ` (${file})`] : []), ...(version ? [version + ` (${file})`] : []),
`// This file was procedurally generated from ${input}.`, `// This file was procedurally generated from ${input}.`,
'', '',
require(template), require(template).trimEnd(),
); );
fs.writeFileSync(output, content); fs.writeFileSync(output, content);
cp.execFileSync('prettier', ['--write', output]); // cp.execFileSync('prettier', ['--write', output]);
} }
// Contracts // Contracts

View File

@ -47,7 +47,7 @@ const sort = type => `\
} }
`; `;
const quickSort = ` const quickSort = `\
/** /**
* @dev Performs a quick sort of a segment of memory. The segment sorted starts at \`begin\` (inclusive), and stops * @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. * at end (exclusive). Sorting follows the \`comp\` comparator.
@ -123,7 +123,7 @@ function _swap(uint256 ptr1, uint256 ptr2) private pure {
} }
`; `;
const defaultComparator = ` const defaultComparator = `\
/// @dev Comparator for sorting arrays in increasing order. /// @dev Comparator for sorting arrays in increasing order.
function _defaultComp(bytes32 a, bytes32 b) private pure returns (bool) { function _defaultComp(bytes32 a, bytes32 b) private pure returns (bool) {
return a < b; return a < b;
@ -150,7 +150,7 @@ const castComparator = type => `\
} }
`; `;
const search = ` const search = `\
/** /**
* @dev Searches a sorted \`array\` and returns the first index that contains * @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 * a value greater or equal to \`element\`. If no such index exists (i.e. all
@ -319,7 +319,7 @@ 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. * @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check.
* *
@ -334,9 +334,10 @@ function unsafeAccess(${type}[] storage arr, uint256 pos) internal pure returns
slot := arr.slot slot := arr.slot
} }
return slot.deriveArray().offset(pos).get${capitalize(type)}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. * @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check.
* *
@ -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. * @dev Helper to set the length of an dynamic array. Directly writing to \`.length\` is forbidden.
* *
@ -360,14 +361,18 @@ function unsafeSetLength(${type}[] storage array, uint256 len) internal {
assembly { assembly {
sstore(array.slot, len) sstore(array.slot, len)
} }
}`; }
`;
// GENERATE // GENERATE
module.exports = format( module.exports = format(
header.trimEnd(), header.trimEnd(),
'library Arrays {', 'library Arrays {',
format(
[].concat(
'using SlotDerivation for bytes32;', 'using SlotDerivation for bytes32;',
'using StorageSlot for bytes32;', 'using StorageSlot for bytes32;',
'',
// sorting, comparator, helpers and internal // sorting, comparator, helpers and internal
sort('bytes32'), sort('bytes32'),
TYPES.filter(type => type !== 'bytes32').map(sort), TYPES.filter(type => type !== 'bytes32').map(sort),
@ -381,5 +386,7 @@ module.exports = format(
TYPES.map(unsafeAccessStorage), TYPES.map(unsafeAccessStorage),
TYPES.map(unsafeAccessMemory), TYPES.map(unsafeAccessMemory),
TYPES.map(unsafeSetLength), TYPES.map(unsafeSetLength),
),
).trimEnd(),
'}', '}',
); );

View File

@ -41,11 +41,7 @@ struct ${opts.checkpointTypeName} {
* IMPORTANT: Never accept \`key\` as a user input, since an arbitrary \`type(${opts.keyTypeName}).max\` key set will disable the * IMPORTANT: Never accept \`key\` as a user input, since an arbitrary \`type(${opts.keyTypeName}).max\` key set will disable the
* library. * library.
*/ */
function push( function push(${opts.historyTypeName} storage self, ${opts.keyTypeName} key, ${opts.valueTypeName} value) internal returns (${opts.valueTypeName}, ${opts.valueTypeName}) {
${opts.historyTypeName} storage self,
${opts.keyTypeName} key,
${opts.valueTypeName} value
) internal returns (${opts.valueTypeName}, ${opts.valueTypeName}) {
return _insert(self.${opts.checkpointFieldName}, key, value); return _insert(self.${opts.checkpointFieldName}, key, value);
} }
@ -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 * @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. * in the most recent checkpoint.
*/ */
function latestCheckpoint(${opts.historyTypeName} storage self) function latestCheckpoint(${opts.historyTypeName} storage self) internal view returns (bool exists, ${opts.keyTypeName} ${opts.keyFieldName}, ${opts.valueTypeName} ${opts.valueFieldName}) {
internal
view
returns (
bool exists,
${opts.keyTypeName} ${opts.keyFieldName},
${opts.valueTypeName} ${opts.valueFieldName}
)
{
uint256 pos = self.${opts.checkpointFieldName}.length; uint256 pos = self.${opts.checkpointFieldName}.length;
if (pos == 0) { if (pos == 0) {
return (false, 0, 0); return (false, 0, 0);
@ -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, * @dev Pushes a (\`key\`, \`value\`) pair into an ordered list of checkpoints, either by inserting a new checkpoint,
* or by updating the last one. * or by updating the last one.
*/ */
function _insert( function _insert(${opts.checkpointTypeName}[] storage self, ${opts.keyTypeName} key, ${opts.valueTypeName} value) private returns (${opts.valueTypeName}, ${opts.valueTypeName}) {
${opts.checkpointTypeName}[] storage self,
${opts.keyTypeName} key,
${opts.valueTypeName} value
) private returns (${opts.valueTypeName}, ${opts.valueTypeName}) {
uint256 pos = self.length; uint256 pos = self.length;
if (pos > 0) { if (pos > 0) {
@ -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. * @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) function _unsafeAccess(
private ${opts.checkpointTypeName}[] storage self,
pure uint256 pos
returns (${opts.checkpointTypeName} storage result) ) private pure returns (${opts.checkpointTypeName} storage result) {
{
assembly { assembly {
mstore(0, self.slot) mstore(0, self.slot)
result.slot := add(keccak256(0, 0x20), pos) result.slot := add(keccak256(0, 0x20), pos)
@ -242,7 +225,11 @@ function _unsafeAccess(${opts.checkpointTypeName}[] storage self, uint256 pos)
module.exports = format( module.exports = format(
header.trimEnd(), header.trimEnd(),
'library Checkpoints {', 'library Checkpoints {',
format(
[].concat(
errors, errors,
OPTS.flatMap(opts => template(opts)), OPTS.map(opts => template(opts)),
),
).trimEnd(),
'}', '}',
); );

View File

@ -22,18 +22,13 @@ uint8 internal constant _KEY_MAX_GAP = 64;
Checkpoints.${opts.historyTypeName} internal _ckpts; Checkpoints.${opts.historyTypeName} internal _ckpts;
// helpers // helpers
function _bound${capitalize(opts.keyTypeName)}( function _bound${capitalize(opts.keyTypeName)}(${opts.keyTypeName} x, ${opts.keyTypeName} min, ${
${opts.keyTypeName} x, opts.keyTypeName
${opts.keyTypeName} min, } max) internal pure returns (${opts.keyTypeName}) {
${opts.keyTypeName} max
) internal pure returns (${opts.keyTypeName}) {
return SafeCast.to${capitalize(opts.keyTypeName)}(bound(uint256(x), uint256(min), uint256(max))); return SafeCast.to${capitalize(opts.keyTypeName)}(bound(uint256(x), uint256(min), uint256(max)));
} }
function _prepareKeys( function _prepareKeys(${opts.keyTypeName}[] memory keys, ${opts.keyTypeName} maxSpread) internal pure {
${opts.keyTypeName}[] memory keys,
${opts.keyTypeName} maxSpread
) internal pure {
${opts.keyTypeName} lastKey = 0; ${opts.keyTypeName} lastKey = 0;
for (uint256 i = 0; i < keys.length; ++i) { for (uint256 i = 0; i < keys.length; ++i) {
${opts.keyTypeName} key = _bound${capitalize(opts.keyTypeName)}(keys[i], lastKey, lastKey + maxSpread); ${opts.keyTypeName} key = _bound${capitalize(opts.keyTypeName)}(keys[i], lastKey, lastKey + maxSpread);
@ -42,11 +37,7 @@ function _prepareKeys(
} }
} }
function _assertLatestCheckpoint( function _assertLatestCheckpoint(bool exist, ${opts.keyTypeName} key, ${opts.valueTypeName} value) internal {
bool exist,
${opts.keyTypeName} key,
${opts.valueTypeName} value
) internal {
(bool _exist, ${opts.keyTypeName} _key, ${opts.valueTypeName} _value) = _ckpts.latestCheckpoint(); (bool _exist, ${opts.keyTypeName} _key, ${opts.valueTypeName} _value) = _ckpts.latestCheckpoint();
assertEq(_exist, exist); assertEq(_exist, exist);
assertEq(_key, key); assertEq(_key, key);
@ -54,11 +45,9 @@ function _assertLatestCheckpoint(
} }
// tests // tests
function testPush( function testPush(${opts.keyTypeName}[] memory keys, ${opts.valueTypeName}[] memory values, ${
${opts.keyTypeName}[] memory keys, opts.keyTypeName
${opts.valueTypeName}[] memory values, } pastKey) public {
${opts.keyTypeName} pastKey
) public {
vm.assume(values.length > 0 && values.length <= keys.length); vm.assume(values.length > 0 && values.length <= keys.length);
_prepareKeys(keys, _KEY_MAX_GAP); _prepareKeys(keys, _KEY_MAX_GAP);
@ -98,11 +87,9 @@ function push(${opts.keyTypeName} key, ${opts.valueTypeName} value) external {
_ckpts.push(key, value); _ckpts.push(key, value);
} }
function testLookup( function testLookup(${opts.keyTypeName}[] memory keys, ${opts.valueTypeName}[] memory values, ${
${opts.keyTypeName}[] memory keys, opts.keyTypeName
${opts.valueTypeName}[] memory values, } lookup) public {
${opts.keyTypeName} lookup
) public {
vm.assume(values.length > 0 && values.length <= keys.length); vm.assume(values.length > 0 && values.length <= keys.length);
_prepareKeys(keys, _KEY_MAX_GAP); _prepareKeys(keys, _KEY_MAX_GAP);
@ -142,5 +129,10 @@ function testLookup(
// GENERATE // GENERATE
module.exports = format( module.exports = format(
header, header,
...OPTS.flatMap(opts => [`contract Checkpoints${opts.historyTypeName}Test is Test {`, [template(opts)], '}']), ...OPTS.flatMap(opts => [
`contract Checkpoints${opts.historyTypeName}Test is Test {`,
[template(opts).trimEnd()],
'}',
'',
]),
); );

View File

@ -54,7 +54,7 @@ import {EnumerableSet} from "./EnumerableSet.sol";
`; `;
/* eslint-enable max-len */ /* eslint-enable max-len */
const defaultMap = () => `\ const defaultMap = `\
// To implement this library for multiple types with as little code repetition as possible, we write it in // To implement this library for multiple types with as little code repetition as possible, we write it in
// terms of a generic Map type with bytes32 keys and values. The Map implementation uses private functions, // terms of a generic Map type with bytes32 keys and values. The Map implementation uses private functions,
// and user-facing implementations such as \`UintToAddressMap\` are just wrappers around the underlying Map. // and user-facing implementations such as \`UintToAddressMap\` are just wrappers around the underlying Map.
@ -78,11 +78,7 @@ struct Bytes32ToBytes32Map {
* Returns true if the key was added to the map, that is if it was not * Returns true if the key was added to the map, that is if it was not
* already present. * already present.
*/ */
function set( function set(Bytes32ToBytes32Map storage map, bytes32 key, bytes32 value) internal returns (bool) {
Bytes32ToBytes32Map storage map,
bytes32 key,
bytes32 value
) internal returns (bool) {
map._values[key] = value; map._values[key] = value;
return map._keys.add(key); return map._keys.add(key);
} }
@ -181,11 +177,7 @@ struct ${name} {
* Returns true if the key was added to the map, that is if it was not * Returns true if the key was added to the map, that is if it was not
* already present. * already present.
*/ */
function set( function set(${name} storage map, ${keyType} key, ${valueType} value) internal returns (bool) {
${name} storage map,
${keyType} key,
${valueType} value
) internal returns (bool) {
return set(map._inner, ${toBytes32(keyType, 'key')}, ${toBytes32(valueType, 'value')}); return set(map._inner, ${toBytes32(keyType, 'key')}, ${toBytes32(valueType, 'value')});
} }
@ -271,11 +263,13 @@ function keys(${name} storage map) internal view returns (${keyType}[] memory) {
module.exports = format( module.exports = format(
header.trimEnd(), header.trimEnd(),
'library EnumerableMap {', 'library EnumerableMap {',
[ format(
[].concat(
'using EnumerableSet for EnumerableSet.Bytes32Set;', 'using EnumerableSet for EnumerableSet.Bytes32Set;',
'', '',
defaultMap(), defaultMap,
TYPES.map(details => customMap(details).trimEnd()).join('\n\n'), TYPES.map(details => customMap(details)),
], ),
).trimEnd(),
'}', '}',
); );

View File

@ -43,7 +43,7 @@ pragma solidity ^0.8.20;
`; `;
/* eslint-enable max-len */ /* eslint-enable max-len */
const defaultSet = () => `\ const defaultSet = `\
// To implement this library for multiple types with as little code // To implement this library for multiple types with as little code
// repetition as possible, we write it in terms of a generic Set type with // repetition as possible, we write it in terms of a generic Set type with
// bytes32 values. // bytes32 values.
@ -240,6 +240,11 @@ function values(${name} storage set) internal view returns (${type}[] memory) {
module.exports = format( module.exports = format(
header.trimEnd(), header.trimEnd(),
'library EnumerableSet {', 'library EnumerableSet {',
[defaultSet(), TYPES.map(details => customSet(details).trimEnd()).join('\n\n')], format(
[].concat(
defaultSet,
TYPES.map(details => customSet(details)),
),
).trimEnd(),
'}', '}',
); );

View File

@ -116,7 +116,7 @@ function toUint${length}(int${length} value) internal pure returns (uint${length
} }
`; `;
const boolToUint = ` const boolToUint = `\
/** /**
* @dev Cast a boolean (false or true) to a uint256 (0 or 1) with no jump. * @dev Cast a boolean (false or true) to a uint256 (0 or 1) with no jump.
*/ */
@ -132,7 +132,8 @@ const boolToUint = `
module.exports = format( module.exports = format(
header.trimEnd(), header.trimEnd(),
'library SafeCast {', 'library SafeCast {',
errors, format(
[...LENGTHS.map(toUintDownCast), toUint(256), ...LENGTHS.map(toIntDownCast), toInt(256), boolToUint], [].concat(errors, LENGTHS.map(toUintDownCast), toUint(256), LENGTHS.map(toIntDownCast), toInt(256), boolToUint),
).trimEnd(),
'}', '}',
); );

View File

@ -109,8 +109,12 @@ function deriveMapping(bytes32 slot, ${type} memory key) internal pure returns (
module.exports = format( module.exports = format(
header.trimEnd(), header.trimEnd(),
'library SlotDerivation {', 'library SlotDerivation {',
format(
[].concat(
namespace, namespace,
array, array,
TYPES.map(type => (type.isValueType ? mapping(type) : mapping2(type))), TYPES.map(type => (type.isValueType ? mapping(type) : mapping2(type))),
),
).trimEnd(),
'}', '}',
); );

View File

@ -90,8 +90,10 @@ function _assertDeriveMapping${name}(${type} memory key) internal {
// GENERATE // GENERATE
module.exports = format( module.exports = format(
header.trimEnd(), header,
'contract SlotDerivationTest is Test, SymTest {', 'contract SlotDerivationTest is Test, SymTest {',
format(
[].concat(
'using SlotDerivation for bytes32;', 'using SlotDerivation for bytes32;',
'', '',
array, array,
@ -105,5 +107,7 @@ module.exports = format(
})), })),
), ),
).map(type => (type.isValueType ? mapping(type) : boundedMapping(type))), ).map(type => (type.isValueType ? mapping(type) : boundedMapping(type))),
),
).trimEnd(),
'}', '}',
); );

View File

@ -86,6 +86,7 @@ const udvt = ({ type, name }) => `\
* @dev UDVT that represent a slot holding a ${type}. * @dev UDVT that represent a slot holding a ${type}.
*/ */
type ${name}SlotType is bytes32; type ${name}SlotType is bytes32;
/** /**
* @dev Cast an arbitrary slot to a ${name}SlotType. * @dev Cast an arbitrary slot to a ${name}SlotType.
*/ */
@ -104,6 +105,7 @@ function tload(${name}SlotType slot) internal view returns (${type} value) {
value := tload(slot) value := tload(slot)
} }
} }
/** /**
* @dev Store \`value\` at location \`slot\` in transient storage. * @dev Store \`value\` at location \`slot\` in transient storage.
*/ */
@ -119,9 +121,13 @@ function tstore(${name}SlotType slot, ${type} value) internal {
module.exports = format( module.exports = format(
header.trimEnd(), header.trimEnd(),
'library StorageSlot {', 'library StorageSlot {',
format(
[].concat(
TYPES.map(type => struct(type)), TYPES.map(type => struct(type)),
TYPES.flatMap(type => [get(type), type.isValueType ? '' : getStorage(type)]), TYPES.flatMap(type => [get(type), !type.isValueType && getStorage(type)].filter(Boolean)),
TYPES.filter(type => type.isValueType).map(type => udvt(type)), TYPES.filter(type => type.isValueType).map(type => udvt(type)),
TYPES.filter(type => type.isValueType).map(type => transient(type)), TYPES.filter(type => type.isValueType).map(type => transient(type)),
),
).trimEnd(),
'}', '}',
); );

View File

@ -54,12 +54,17 @@ function tstore(bytes32 slot, ${type} value) public {
// GENERATE // GENERATE
module.exports = format( module.exports = format(
header.trimEnd(), header,
'contract StorageSlotMock is Multicall {', 'contract StorageSlotMock is Multicall {',
format(
[].concat(
'using StorageSlot for *;', 'using StorageSlot for *;',
'',
TYPES.filter(type => type.isValueType).map(type => storageSetValueType(type)), TYPES.filter(type => type.isValueType).map(type => storageSetValueType(type)),
TYPES.filter(type => type.isValueType).map(type => storageGetValueType(type)), TYPES.filter(type => type.isValueType).map(type => storageGetValueType(type)),
TYPES.filter(type => !type.isValueType).map(type => storageSetNonValueType(type)), TYPES.filter(type => !type.isValueType).map(type => storageSetNonValueType(type)),
TYPES.filter(type => type.isValueType).map(type => transient(type)), TYPES.filter(type => type.isValueType).map(type => transient(type)),
),
).trimEnd(),
'}', '}',
); );