Update docs

This commit is contained in:
github-actions
2023-09-19 19:19:10 +00:00
commit dbe796d542
624 changed files with 107720 additions and 0 deletions

View File

@ -0,0 +1,247 @@
const format = require('../format-lines');
const { OPTS } = require('./Checkpoints.opts.js');
// TEMPLATE
const header = `\
pragma solidity ^0.8.20;
import {Math} from "../math/Math.sol";
/**
* @dev This library defines the \`Trace*\` struct, for checkpointing values as they change at different points in
* time, and later looking up past values by block number. See {Votes} as an example.
*
* To create a history of checkpoints define a variable type \`Checkpoints.Trace*\` in your contract, and store a new
* checkpoint for the current transaction block using the {push} function.
*/
`;
const errors = `\
/**
* @dev A value was attempted to be inserted on a past checkpoint.
*/
error CheckpointUnorderedInsertion();
`;
const template = opts => `\
struct ${opts.historyTypeName} {
${opts.checkpointTypeName}[] ${opts.checkpointFieldName};
}
struct ${opts.checkpointTypeName} {
${opts.keyTypeName} ${opts.keyFieldName};
${opts.valueTypeName} ${opts.valueFieldName};
}
/**
* @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}) {
return _insert(self.${opts.checkpointFieldName}, key, value);
}
/**
* @dev Returns the value in the first (oldest) checkpoint with key greater or equal than the search key, or zero if
* there is none.
*/
function lowerLookup(${opts.historyTypeName} storage self, ${opts.keyTypeName} key) internal view returns (${opts.valueTypeName}) {
uint256 len = self.${opts.checkpointFieldName}.length;
uint256 pos = _lowerBinaryLookup(self.${opts.checkpointFieldName}, key, 0, len);
return pos == len ? 0 : _unsafeAccess(self.${opts.checkpointFieldName}, pos).${opts.valueFieldName};
}
/**
* @dev Returns the value in the last (most recent) checkpoint with key lower or equal than the search key, or zero
* if there is none.
*/
function upperLookup(${opts.historyTypeName} storage self, ${opts.keyTypeName} key) internal view returns (${opts.valueTypeName}) {
uint256 len = self.${opts.checkpointFieldName}.length;
uint256 pos = _upperBinaryLookup(self.${opts.checkpointFieldName}, key, 0, len);
return pos == 0 ? 0 : _unsafeAccess(self.${opts.checkpointFieldName}, pos - 1).${opts.valueFieldName};
}
/**
* @dev Returns the value in the last (most recent) checkpoint with key lower or equal than the search key, or zero
* if there is none.
*
* NOTE: This is a variant of {upperLookup} that is optimised to find "recent" checkpoint (checkpoints with high
* keys).
*/
function upperLookupRecent(${opts.historyTypeName} storage self, ${opts.keyTypeName} key) internal view returns (${opts.valueTypeName}) {
uint256 len = self.${opts.checkpointFieldName}.length;
uint256 low = 0;
uint256 high = len;
if (len > 5) {
uint256 mid = len - Math.sqrt(len);
if (key < _unsafeAccess(self.${opts.checkpointFieldName}, mid)._key) {
high = mid;
} else {
low = mid + 1;
}
}
uint256 pos = _upperBinaryLookup(self.${opts.checkpointFieldName}, key, low, high);
return pos == 0 ? 0 : _unsafeAccess(self.${opts.checkpointFieldName}, pos - 1).${opts.valueFieldName};
}
/**
* @dev Returns the value in the most recent checkpoint, or zero if there are no checkpoints.
*/
function latest(${opts.historyTypeName} storage self) internal view returns (${opts.valueTypeName}) {
uint256 pos = self.${opts.checkpointFieldName}.length;
return pos == 0 ? 0 : _unsafeAccess(self.${opts.checkpointFieldName}, pos - 1).${opts.valueFieldName};
}
/**
* @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}
)
{
uint256 pos = self.${opts.checkpointFieldName}.length;
if (pos == 0) {
return (false, 0, 0);
} else {
${opts.checkpointTypeName} memory ckpt = _unsafeAccess(self.${opts.checkpointFieldName}, pos - 1);
return (true, ckpt.${opts.keyFieldName}, ckpt.${opts.valueFieldName});
}
}
/**
* @dev Returns the number of checkpoint.
*/
function length(${opts.historyTypeName} storage self) internal view returns (uint256) {
return self.${opts.checkpointFieldName}.length;
}
/**
* @dev Returns checkpoint at given position.
*/
function at(${opts.historyTypeName} storage self, uint32 pos) internal view returns (${opts.checkpointTypeName} memory) {
return self.${opts.checkpointFieldName}[pos];
}
/**
* @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}) {
uint256 pos = self.length;
if (pos > 0) {
// Copying to memory is important here.
${opts.checkpointTypeName} memory last = _unsafeAccess(self, pos - 1);
// Checkpoint keys must be non-decreasing.
if(last.${opts.keyFieldName} > key) {
revert CheckpointUnorderedInsertion();
}
// Update or push new checkpoint
if (last.${opts.keyFieldName} == key) {
_unsafeAccess(self, pos - 1).${opts.valueFieldName} = value;
} else {
self.push(${opts.checkpointTypeName}({${opts.keyFieldName}: key, ${opts.valueFieldName}: value}));
}
return (last.${opts.valueFieldName}, value);
} else {
self.push(${opts.checkpointTypeName}({${opts.keyFieldName}: key, ${opts.valueFieldName}: value}));
return (0, value);
}
}
/**
* @dev Return the index of the last (most recent) checkpoint with key lower or equal than the search key, or \`high\`
* if there is none. \`low\` and \`high\` define a section where to do the search, with inclusive \`low\` and exclusive
* \`high\`.
*
* WARNING: \`high\` should not be greater than the array's length.
*/
function _upperBinaryLookup(
${opts.checkpointTypeName}[] storage self,
${opts.keyTypeName} key,
uint256 low,
uint256 high
) private view returns (uint256) {
while (low < high) {
uint256 mid = Math.average(low, high);
if (_unsafeAccess(self, mid).${opts.keyFieldName} > key) {
high = mid;
} else {
low = mid + 1;
}
}
return high;
}
/**
* @dev Return the index of the first (oldest) checkpoint with key is greater or equal than the search key, or
* \`high\` if there is none. \`low\` and \`high\` define a section where to do the search, with inclusive \`low\` and
* exclusive \`high\`.
*
* WARNING: \`high\` should not be greater than the array's length.
*/
function _lowerBinaryLookup(
${opts.checkpointTypeName}[] storage self,
${opts.keyTypeName} key,
uint256 low,
uint256 high
) private view returns (uint256) {
while (low < high) {
uint256 mid = Math.average(low, high);
if (_unsafeAccess(self, mid).${opts.keyFieldName} < key) {
low = mid + 1;
} else {
high = mid;
}
}
return high;
}
/**
* @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)
{
assembly {
mstore(0, self.slot)
result.slot := add(keccak256(0, 0x20), pos)
}
}
`;
/* eslint-enable max-len */
// GENERATE
module.exports = format(
header.trimEnd(),
'library Checkpoints {',
errors,
OPTS.flatMap(opts => template(opts)),
'}',
);

View File

@ -0,0 +1,17 @@
// OPTIONS
const VALUE_SIZES = [224, 208, 160];
const defaultOpts = size => ({
historyTypeName: `Trace${size}`,
checkpointTypeName: `Checkpoint${size}`,
checkpointFieldName: '_checkpoints',
keyTypeName: `uint${256 - size}`,
keyFieldName: '_key',
valueTypeName: `uint${size}`,
valueFieldName: '_value',
});
module.exports = {
VALUE_SIZES,
OPTS: VALUE_SIZES.map(size => defaultOpts(size)),
};

View File

@ -0,0 +1,146 @@
const format = require('../format-lines');
const { capitalize } = require('../../helpers');
const { OPTS } = require('./Checkpoints.opts.js');
// TEMPLATE
const header = `\
pragma solidity ^0.8.20;
import {Test} from "forge-std/Test.sol";
import {SafeCast} from "../../../contracts/utils/math/SafeCast.sol";
import {Checkpoints} from "../../../contracts/utils/structs/Checkpoints.sol";
`;
/* eslint-disable max-len */
const template = opts => `\
using Checkpoints for Checkpoints.${opts.historyTypeName};
// Maximum gap between keys used during the fuzzing tests: the \`_prepareKeys\` function with make sure that
// key#n+1 is in the [key#n, key#n + _KEY_MAX_GAP] range.
uint8 internal constant _KEY_MAX_GAP = 64;
Checkpoints.${opts.historyTypeName} internal _ckpts;
// helpers
function _bound${capitalize(opts.keyTypeName)}(
${opts.keyTypeName} x,
${opts.keyTypeName} min,
${opts.keyTypeName} max
) internal view returns (${opts.keyTypeName}) {
return SafeCast.to${capitalize(opts.keyTypeName)}(bound(uint256(x), uint256(min), uint256(max)));
}
function _prepareKeys(
${opts.keyTypeName}[] memory keys,
${opts.keyTypeName} maxSpread
) internal view {
${opts.keyTypeName} lastKey = 0;
for (uint256 i = 0; i < keys.length; ++i) {
${opts.keyTypeName} key = _bound${capitalize(opts.keyTypeName)}(keys[i], lastKey, lastKey + maxSpread);
keys[i] = key;
lastKey = key;
}
}
function _assertLatestCheckpoint(
bool exist,
${opts.keyTypeName} key,
${opts.valueTypeName} value
) internal {
(bool _exist, ${opts.keyTypeName} _key, ${opts.valueTypeName} _value) = _ckpts.latestCheckpoint();
assertEq(_exist, exist);
assertEq(_key, key);
assertEq(_value, value);
}
// tests
function testPush(
${opts.keyTypeName}[] memory keys,
${opts.valueTypeName}[] memory values,
${opts.keyTypeName} pastKey
) public {
vm.assume(values.length > 0 && values.length <= keys.length);
_prepareKeys(keys, _KEY_MAX_GAP);
// initial state
assertEq(_ckpts.length(), 0);
assertEq(_ckpts.latest(), 0);
_assertLatestCheckpoint(false, 0, 0);
uint256 duplicates = 0;
for (uint256 i = 0; i < keys.length; ++i) {
${opts.keyTypeName} key = keys[i];
${opts.valueTypeName} value = values[i % values.length];
if (i > 0 && key == keys[i-1]) ++duplicates;
// push
_ckpts.push(key, value);
// check length & latest
assertEq(_ckpts.length(), i + 1 - duplicates);
assertEq(_ckpts.latest(), value);
_assertLatestCheckpoint(true, key, value);
}
if (keys.length > 0) {
${opts.keyTypeName} lastKey = keys[keys.length - 1];
if (lastKey > 0) {
pastKey = _bound${capitalize(opts.keyTypeName)}(pastKey, 0, lastKey - 1);
vm.expectRevert();
this.push(pastKey, values[keys.length % values.length]);
}
}
}
// used to test reverts
function push(${opts.keyTypeName} key, ${opts.valueTypeName} value) external {
_ckpts.push(key, value);
}
function testLookup(
${opts.keyTypeName}[] memory keys,
${opts.valueTypeName}[] memory values,
${opts.keyTypeName} lookup
) public {
vm.assume(values.length > 0 && values.length <= keys.length);
_prepareKeys(keys, _KEY_MAX_GAP);
${opts.keyTypeName} lastKey = keys.length == 0 ? 0 : keys[keys.length - 1];
lookup = _bound${capitalize(opts.keyTypeName)}(lookup, 0, lastKey + _KEY_MAX_GAP);
${opts.valueTypeName} upper = 0;
${opts.valueTypeName} lower = 0;
${opts.keyTypeName} lowerKey = type(${opts.keyTypeName}).max;
for (uint256 i = 0; i < keys.length; ++i) {
${opts.keyTypeName} key = keys[i];
${opts.valueTypeName} value = values[i % values.length];
// push
_ckpts.push(key, value);
// track expected result of lookups
if (key <= lookup) {
upper = value;
}
// find the first key that is not smaller than the lookup key
if (key >= lookup && (i == 0 || keys[i-1] < lookup)) {
lowerKey = key;
}
if (key == lowerKey) {
lower = value;
}
}
// check lookup
assertEq(_ckpts.lowerLookup(lookup), lower);
assertEq(_ckpts.upperLookup(lookup), upper);
assertEq(_ckpts.upperLookupRecent(lookup), upper);
}
`;
// GENERATE
module.exports = format(
header,
...OPTS.flatMap(opts => [`contract Checkpoints${opts.historyTypeName}Test is Test {`, [template(opts)], '}']),
);

View File

@ -0,0 +1,283 @@
const format = require('../format-lines');
const { fromBytes32, toBytes32 } = require('./conversion');
const TYPES = [
{ name: 'UintToUintMap', keyType: 'uint256', valueType: 'uint256' },
{ name: 'UintToAddressMap', keyType: 'uint256', valueType: 'address' },
{ name: 'AddressToUintMap', keyType: 'address', valueType: 'uint256' },
{ name: 'Bytes32ToUintMap', keyType: 'bytes32', valueType: 'uint256' },
];
/* eslint-disable max-len */
const header = `\
pragma solidity ^0.8.20;
import {EnumerableSet} from "./EnumerableSet.sol";
/**
* @dev Library for managing an enumerable variant of Solidity's
* https://solidity.readthedocs.io/en/latest/types.html#mapping-types[\`mapping\`]
* type.
*
* Maps have the following properties:
*
* - Entries are added, removed, and checked for existence in constant time
* (O(1)).
* - Entries are enumerated in O(n). No guarantees are made on the ordering.
*
* \`\`\`solidity
* contract Example {
* // Add the library methods
* using EnumerableMap for EnumerableMap.UintToAddressMap;
*
* // Declare a set state variable
* EnumerableMap.UintToAddressMap private myMap;
* }
* \`\`\`
*
* The following map types are supported:
*
* - \`uint256 -> address\` (\`UintToAddressMap\`) since v3.0.0
* - \`address -> uint256\` (\`AddressToUintMap\`) since v4.6.0
* - \`bytes32 -> bytes32\` (\`Bytes32ToBytes32Map\`) since v4.6.0
* - \`uint256 -> uint256\` (\`UintToUintMap\`) since v4.7.0
* - \`bytes32 -> uint256\` (\`Bytes32ToUintMap\`) since v4.7.0
*
* [WARNING]
* ====
* Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
* unusable.
* See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
*
* In order to clean an EnumerableMap, you can either remove all elements one by one or create a fresh instance using an
* array of EnumerableMap.
* ====
*/
`;
/* eslint-enable max-len */
const defaultMap = () => `\
// 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,
// and user-facing implementations such as \`UintToAddressMap\` are just wrappers around the underlying Map.
// This means that we can only create new EnumerableMaps for types that fit in bytes32.
/**
* @dev Query for a nonexistent map key.
*/
error EnumerableMapNonexistentKey(bytes32 key);
struct Bytes32ToBytes32Map {
// Storage of keys
EnumerableSet.Bytes32Set _keys;
mapping(bytes32 key => bytes32) _values;
}
/**
* @dev Adds a key-value pair to a map, or updates the value for an existing
* key. O(1).
*
* Returns true if the key was added to the map, that is if it was not
* already present.
*/
function set(
Bytes32ToBytes32Map storage map,
bytes32 key,
bytes32 value
) internal returns (bool) {
map._values[key] = value;
return map._keys.add(key);
}
/**
* @dev Removes a key-value pair from a map. O(1).
*
* Returns true if the key was removed from the map, that is if it was present.
*/
function remove(Bytes32ToBytes32Map storage map, bytes32 key) internal returns (bool) {
delete map._values[key];
return map._keys.remove(key);
}
/**
* @dev Returns true if the key is in the map. O(1).
*/
function contains(Bytes32ToBytes32Map storage map, bytes32 key) internal view returns (bool) {
return map._keys.contains(key);
}
/**
* @dev Returns the number of key-value pairs in the map. O(1).
*/
function length(Bytes32ToBytes32Map storage map) internal view returns (uint256) {
return map._keys.length();
}
/**
* @dev Returns the key-value pair stored at position \`index\` in the map. O(1).
*
* Note that there are no guarantees on the ordering of entries inside the
* array, and it may change when more entries are added or removed.
*
* Requirements:
*
* - \`index\` must be strictly less than {length}.
*/
function at(Bytes32ToBytes32Map storage map, uint256 index) internal view returns (bytes32, bytes32) {
bytes32 key = map._keys.at(index);
return (key, map._values[key]);
}
/**
* @dev Tries to returns the value associated with \`key\`. O(1).
* Does not revert if \`key\` is not in the map.
*/
function tryGet(Bytes32ToBytes32Map storage map, bytes32 key) internal view returns (bool, bytes32) {
bytes32 value = map._values[key];
if (value == bytes32(0)) {
return (contains(map, key), bytes32(0));
} else {
return (true, value);
}
}
/**
* @dev Returns the value associated with \`key\`. O(1).
*
* Requirements:
*
* - \`key\` must be in the map.
*/
function get(Bytes32ToBytes32Map storage map, bytes32 key) internal view returns (bytes32) {
bytes32 value = map._values[key];
if(value == 0 && !contains(map, key)) {
revert EnumerableMapNonexistentKey(key);
}
return value;
}
/**
* @dev Return the an array containing all the keys
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function keys(Bytes32ToBytes32Map storage map) internal view returns (bytes32[] memory) {
return map._keys.values();
}
`;
const customMap = ({ name, keyType, valueType }) => `\
// ${name}
struct ${name} {
Bytes32ToBytes32Map _inner;
}
/**
* @dev Adds a key-value pair to a map, or updates the value for an existing
* key. O(1).
*
* Returns true if the key was added to the map, that is if it was not
* already present.
*/
function set(
${name} storage map,
${keyType} key,
${valueType} value
) internal returns (bool) {
return set(map._inner, ${toBytes32(keyType, 'key')}, ${toBytes32(valueType, 'value')});
}
/**
* @dev Removes a value from a map. O(1).
*
* Returns true if the key was removed from the map, that is if it was present.
*/
function remove(${name} storage map, ${keyType} key) internal returns (bool) {
return remove(map._inner, ${toBytes32(keyType, 'key')});
}
/**
* @dev Returns true if the key is in the map. O(1).
*/
function contains(${name} storage map, ${keyType} key) internal view returns (bool) {
return contains(map._inner, ${toBytes32(keyType, 'key')});
}
/**
* @dev Returns the number of elements in the map. O(1).
*/
function length(${name} storage map) internal view returns (uint256) {
return length(map._inner);
}
/**
* @dev Returns the element stored at position \`index\` in the map. O(1).
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - \`index\` must be strictly less than {length}.
*/
function at(${name} storage map, uint256 index) internal view returns (${keyType}, ${valueType}) {
(bytes32 key, bytes32 value) = at(map._inner, index);
return (${fromBytes32(keyType, 'key')}, ${fromBytes32(valueType, 'value')});
}
/**
* @dev Tries to returns the value associated with \`key\`. O(1).
* Does not revert if \`key\` is not in the map.
*/
function tryGet(${name} storage map, ${keyType} key) internal view returns (bool, ${valueType}) {
(bool success, bytes32 value) = tryGet(map._inner, ${toBytes32(keyType, 'key')});
return (success, ${fromBytes32(valueType, 'value')});
}
/**
* @dev Returns the value associated with \`key\`. O(1).
*
* Requirements:
*
* - \`key\` must be in the map.
*/
function get(${name} storage map, ${keyType} key) internal view returns (${valueType}) {
return ${fromBytes32(valueType, `get(map._inner, ${toBytes32(keyType, 'key')})`)};
}
/**
* @dev Return the an array containing all the keys
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function keys(${name} storage map) internal view returns (${keyType}[] memory) {
bytes32[] memory store = keys(map._inner);
${keyType}[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
`;
// GENERATE
module.exports = format(
header.trimEnd(),
'library EnumerableMap {',
[
'using EnumerableSet for EnumerableSet.Bytes32Set;',
'',
defaultMap(),
TYPES.map(details => customMap(details).trimEnd()).join('\n\n'),
],
'}',
);

View File

@ -0,0 +1,250 @@
const format = require('../format-lines');
const { fromBytes32, toBytes32 } = require('./conversion');
const TYPES = [
{ name: 'Bytes32Set', type: 'bytes32' },
{ name: 'AddressSet', type: 'address' },
{ name: 'UintSet', type: 'uint256' },
];
/* eslint-disable max-len */
const header = `\
pragma solidity ^0.8.20;
/**
* @dev Library for managing
* https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
* types.
*
* Sets have the following properties:
*
* - Elements are added, removed, and checked for existence in constant time
* (O(1)).
* - Elements are enumerated in O(n). No guarantees are made on the ordering.
*
* \`\`\`solidity
* contract Example {
* // Add the library methods
* using EnumerableSet for EnumerableSet.AddressSet;
*
* // Declare a set state variable
* EnumerableSet.AddressSet private mySet;
* }
* \`\`\`
*
* As of v3.3.0, sets of type \`bytes32\` (\`Bytes32Set\`), \`address\` (\`AddressSet\`)
* and \`uint256\` (\`UintSet\`) are supported.
*
* [WARNING]
* ====
* Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
* unusable.
* See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
*
* In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an
* array of EnumerableSet.
* ====
*/
`;
/* eslint-enable max-len */
const defaultSet = () => `\
// 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
// bytes32 values.
// The Set implementation uses private functions, and user-facing
// implementations (such as AddressSet) are just wrappers around the
// underlying Set.
// This means that we can only create new EnumerableSets for types that fit
// in bytes32.
struct Set {
// Storage of set values
bytes32[] _values;
// Position is the index of the value in the \`values\` array plus 1.
// Position 0 is used to mean a value is not in the set.
mapping(bytes32 value => uint256) _positions;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function _add(Set storage set, bytes32 value) private returns (bool) {
if (!_contains(set, value)) {
set._values.push(value);
// The value is stored at length-1, but we add 1 to all indexes
// and use 0 as a sentinel value
set._positions[value] = set._values.length;
return true;
} else {
return false;
}
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function _remove(Set storage set, bytes32 value) private returns (bool) {
// We cache the value's position to prevent multiple reads from the same storage slot
uint256 position = set._positions[value];
if (position != 0) {
// Equivalent to contains(set, value)
// To delete an element from the _values array in O(1), we swap the element to delete with the last one in
// the array, and then remove the last element (sometimes called as 'swap and pop').
// This modifies the order of the array, as noted in {at}.
uint256 valueIndex = position - 1;
uint256 lastIndex = set._values.length - 1;
if (valueIndex != lastIndex) {
bytes32 lastValue = set._values[lastIndex];
// Move the lastValue to the index where the value to delete is
set._values[valueIndex] = lastValue;
// Update the tracked position of the lastValue (that was just moved)
set._positions[lastValue] = position;
}
// Delete the slot where the moved value was stored
set._values.pop();
// Delete the tracked position for the deleted slot
delete set._positions[value];
return true;
} else {
return false;
}
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function _contains(Set storage set, bytes32 value) private view returns (bool) {
return set._positions[value] != 0;
}
/**
* @dev Returns the number of values on the set. O(1).
*/
function _length(Set storage set) private view returns (uint256) {
return set._values.length;
}
/**
* @dev Returns the value stored at position \`index\` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - \`index\` must be strictly less than {length}.
*/
function _at(Set storage set, uint256 index) private view returns (bytes32) {
return set._values[index];
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function _values(Set storage set) private view returns (bytes32[] memory) {
return set._values;
}
`;
const customSet = ({ name, type }) => `\
// ${name}
struct ${name} {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(${name} storage set, ${type} value) internal returns (bool) {
return _add(set._inner, ${toBytes32(type, 'value')});
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(${name} storage set, ${type} value) internal returns (bool) {
return _remove(set._inner, ${toBytes32(type, 'value')});
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(${name} storage set, ${type} value) internal view returns (bool) {
return _contains(set._inner, ${toBytes32(type, 'value')});
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(${name} storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position \`index\` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - \`index\` must be strictly less than {length}.
*/
function at(${name} storage set, uint256 index) internal view returns (${type}) {
return ${fromBytes32(type, '_at(set._inner, index)')};
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(${name} storage set) internal view returns (${type}[] memory) {
bytes32[] memory store = _values(set._inner);
${type}[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
`;
// GENERATE
module.exports = format(
header.trimEnd(),
'library EnumerableSet {',
[defaultSet(), TYPES.map(details => customSet(details).trimEnd()).join('\n\n')],
'}',
);

View File

@ -0,0 +1,126 @@
const format = require('../format-lines');
const { range } = require('../../helpers');
const LENGTHS = range(8, 256, 8).reverse(); // 248 → 8 (in steps of 8)
const header = `\
pragma solidity ^0.8.20;
/**
* @dev Wrappers over Solidity's uintXX/intXX casting operators with added overflow
* checks.
*
* Downcasting from uint256/int256 in Solidity does not revert on overflow. This can
* easily result in undesired exploitation or bugs, since developers usually
* assume that overflows raise errors. \`SafeCast\` restores this intuition by
* reverting the transaction when such an operation overflows.
*
* Using this library instead of the unchecked operations eliminates an entire
* class of bugs, so it's recommended to use it always.
*/
`;
const errors = `\
/**
* @dev Value doesn't fit in an uint of \`bits\` size.
*/
error SafeCastOverflowedUintDowncast(uint8 bits, uint256 value);
/**
* @dev An int value doesn't fit in an uint of \`bits\` size.
*/
error SafeCastOverflowedIntToUint(int256 value);
/**
* @dev Value doesn't fit in an int of \`bits\` size.
*/
error SafeCastOverflowedIntDowncast(uint8 bits, int256 value);
/**
* @dev An uint value doesn't fit in an int of \`bits\` size.
*/
error SafeCastOverflowedUintToInt(uint256 value);
`;
const toUintDownCast = length => `\
/**
* @dev Returns the downcasted uint${length} from uint256, reverting on
* overflow (when the input is greater than largest uint${length}).
*
* Counterpart to Solidity's \`uint${length}\` operator.
*
* Requirements:
*
* - input must fit into ${length} bits
*/
function toUint${length}(uint256 value) internal pure returns (uint${length}) {
if (value > type(uint${length}).max) {
revert SafeCastOverflowedUintDowncast(${length}, value);
}
return uint${length}(value);
}
`;
/* eslint-disable max-len */
const toIntDownCast = length => `\
/**
* @dev Returns the downcasted int${length} from int256, reverting on
* overflow (when the input is less than smallest int${length} or
* greater than largest int${length}).
*
* Counterpart to Solidity's \`int${length}\` operator.
*
* Requirements:
*
* - input must fit into ${length} bits
*/
function toInt${length}(int256 value) internal pure returns (int${length} downcasted) {
downcasted = int${length}(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(${length}, value);
}
}
`;
/* eslint-enable max-len */
const toInt = length => `\
/**
* @dev Converts an unsigned uint${length} into a signed int${length}.
*
* Requirements:
*
* - input must be less than or equal to maxInt${length}.
*/
function toInt${length}(uint${length} value) internal pure returns (int${length}) {
// Note: Unsafe cast below is okay because \`type(int${length}).max\` is guaranteed to be positive
if (value > uint${length}(type(int${length}).max)) {
revert SafeCastOverflowedUintToInt(value);
}
return int${length}(value);
}
`;
const toUint = length => `\
/**
* @dev Converts a signed int${length} into an unsigned uint${length}.
*
* Requirements:
*
* - input must be greater than or equal to 0.
*/
function toUint${length}(int${length} value) internal pure returns (uint${length}) {
if (value < 0) {
revert SafeCastOverflowedIntToUint(value);
}
return uint${length}(value);
}
`;
// GENERATE
module.exports = format(
header.trimEnd(),
'library SafeCast {',
errors,
[...LENGTHS.map(toUintDownCast), toUint(256), ...LENGTHS.map(toIntDownCast), toInt(256)],
'}',
);

View File

@ -0,0 +1,78 @@
const format = require('../format-lines');
const { capitalize } = require('../../helpers');
const TYPES = [
{ type: 'address', isValueType: true },
{ type: 'bool', isValueType: true, name: 'Boolean' },
{ type: 'bytes32', isValueType: true },
{ type: 'uint256', isValueType: true },
{ type: 'string', isValueType: false },
{ type: 'bytes', isValueType: false },
].map(type => Object.assign(type, { struct: (type.name ?? capitalize(type.type)) + 'Slot' }));
const header = `\
pragma solidity ^0.8.20;
/**
* @dev Library for reading and writing primitive types to specific storage slots.
*
* Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.
* This library helps with reading and writing to such slots without the need for inline assembly.
*
* The functions in this library return Slot structs that contain a \`value\` member that can be used to read or write.
*
* Example usage to set ERC1967 implementation slot:
* \`\`\`solidity
* contract ERC1967 {
* bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
*
* function _getImplementation() internal view returns (address) {
* return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
* }
*
* function _setImplementation(address newImplementation) internal {
* require(newImplementation.code.length > 0);
* StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
* }
* }
* \`\`\`
*/
`;
const struct = type => `\
struct ${type.struct} {
${type.type} value;
}
`;
const get = type => `\
/**
* @dev Returns an \`${type.struct}\` with member \`value\` located at \`slot\`.
*/
function get${type.struct}(bytes32 slot) internal pure returns (${type.struct} storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := slot
}
}
`;
const getStorage = type => `\
/**
* @dev Returns an \`${type.struct}\` representation of the ${type.type} storage pointer \`store\`.
*/
function get${type.struct}(${type.type} storage store) internal pure returns (${type.struct} storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := store.slot
}
}
`;
// GENERATE
module.exports = format(
header.trimEnd(),
'library StorageSlot {',
[...TYPES.map(struct), ...TYPES.flatMap(type => [get(type), type.isValueType ? '' : getStorage(type)])],
'}',
);

View File

@ -0,0 +1,30 @@
function toBytes32(type, value) {
switch (type) {
case 'bytes32':
return value;
case 'uint256':
return `bytes32(${value})`;
case 'address':
return `bytes32(uint256(uint160(${value})))`;
default:
throw new Error(`Conversion from ${type} to bytes32 not supported`);
}
}
function fromBytes32(type, value) {
switch (type) {
case 'bytes32':
return value;
case 'uint256':
return `uint256(${value})`;
case 'address':
return `address(uint160(uint256(${value})))`;
default:
throw new Error(`Conversion from bytes32 to ${type} not supported`);
}
}
module.exports = {
toBytes32,
fromBytes32,
};