Multiple address/ID mappings can be combined into a single mapping of an address/ID to a struct, where appropriate
1
[G‑02]
State variables only set in the constructor should be declared immutable
14
[G‑03]
Structs can be packed into fewer storage slots
1
[G‑04]
Using calldata instead of memory for read-only arguments in external functions saves gas
2
[G‑05]
Using storage instead of memory for structs/arrays saves gas
9
[G‑06]
Avoid contract existence checks by using solidity version 0.8.10 or later
3
[G‑07]
State variables should be cached in stack variables rather than re-reading them from storage
3
[G‑08]
<x> += <y> costs more gas than <x> = <x> + <y> for state variables
1
[G‑09]
internal functions only called once can be inlined to save gas
3
[G‑10]
Add unchecked {} for subtractions where the operands cannot underflow because of a previous require() or if-statement
2
[G‑11]
++i/i++ should be unchecked{++i}/unchecked{i++} when it is not possible for them to overflow, as is the case when used in for- and while-loops
4
[G‑12]
Optimize names to save gas
4
[G‑13]
Using bools for storage incurs overhead
1
[G‑14]
Use a more recent version of solidity
5
[G‑15]
Using > 0 costs more gas than != 0 when used on a uint in a require() statement
2
[G‑16]
++i costs less gas than i++, especially when it's used in for-loops (--i/i-- too)
4
[G‑17]
Usage of uints/ints smaller than 32 bytes (256 bits) incurs overhead
3
[G‑18]
Using private rather than public for constants, saves gas
3
[G‑19]
Division by two should use bit shifting
2
[G‑20]
Stack variable used as a cheaper cache for a state variable is only used once
1
[G‑21]
require() or revert() statements that check input arguments should be at the top of the function
2
[G‑22]
Superfluous event fields
2
[G‑23]
Use custom errors rather than revert()/require() strings to save gas
42
Total: 114 instances over 23 issues
Gas Optimizations
[G‑01] Multiple address/ID mappings can be combined into a single mapping of an address/ID to a struct, where appropriate
Saves a storage slot for the mapping. Depending on the circumstances and sizes of types, can avoid a Gsset (20000 gas) per mapping combined. Reads and subsequent writes can also be cheaper when a function requires both values and they both fit in the same storage slot. Finally, if both fields are accessed in the same function, can save ~42 gas per access due to not having to recalculate the key's keccak256 hash (Gkeccak256 - 30 gas) and that calculation's associated stack operations.
There is 1 instance of this issue:
File: contracts/VotingEscrow.sol
58 mapping(address => Point[1000000000]) public userPointHistory;
59: mapping(address => uint256) public userPointEpoch;
[G‑02] State variables only set in the constructor should be declared immutable
Avoids a Gsset (20000 gas) in the constructor, and replaces the first access in each transaction (Gcoldsload - 2100 gas) and each access thereafter (Gwarmacces - 100 gas) with a PUSH32 (3 gas).
[G‑04] Using calldata instead of memory for read-only arguments in external functions saves gas
When a function with a memory array is called externally, the abi.decode() step has to use a for-loop to copy each index of the calldata to the memory index. Each iteration of this for-loop costs at least 60 gas (i.e. 60 * <mem_array>.length). Using calldata directly, obliviates the need for such a loop in the contract code and runtime execution. Note that even if an interface defines a function as having memory arguments, it's still valid for implementation contracs to use calldata arguments instead.
If the array is passed to an internal function which passes the array to another internal function where the array is modified and therefore memory is used in the external call, it's still more gass-efficient to use calldata when the external function uses modifiers, since the modifiers may prevent the internal functions from being called. Structs have the same overhead as an array of length one
Note that I've also flagged instances where the function is public but can be marked as external since it's not called by the contract, and cases where a constructor is involved
[G‑05] Using storage instead of memory for structs/arrays saves gas
When fetching data from a storage location, assigning the data to a memory variable causes all fields of the struct/array to be read from storage, which incurs a Gcoldsload (2100 gas) for each field of the struct/array. If the fields are read from the new memory variable, they incur an additional MLOAD rather than a cheap stack read. Instead of declearing the variable with the memory keyword, declaring the variable with the storage keyword and caching any fields that need to be re-read in stack variables, will be much cheaper, only incuring the Gcoldsload for the fields actually read. The only time it makes sense to read the whole struct/array into a memory variable, is if the full struct/array is being returned by the function, is being passed to a function that requires memory, or if the array/struct is being read from another memory array/struct
[G‑06] Avoid contract existence checks by using solidity version 0.8.10 or later
Prior to 0.8.10 the compiler inserted extra code, including EXTCODESIZE (100 gas), to check for contract existence for external calls. In more recent solidity versions, the compiler will not insert these checks if the external call has a return value
[G‑07] State variables should be cached in stack variables rather than re-reading them from storage
The instances below point to the second+ access of a state variable within a function. Caching of a state variable replace each Gwarmaccess (100 gas) with a much cheaper stack read. Other less obvious fixes/optimizations include having local memory caches of state variable structs, or having local caches of state variable contracts/addresses.
There are 3 instances of this issue:
File: contracts/VotingEscrow.sol
/// @audit penaltyRecipient on line 676
677: emit CollectPenalty(amount, penaltyRecipient);
/// @audit pointHistory on line 788
796: Point memory point1 = pointHistory[epoch + 1];
/// @audit pointHistory on line 882
891: Point memory pointNext = pointHistory[targetEpoch + 1];
[G‑11] ++i/i++ should be unchecked{++i}/unchecked{i++} when it is not possible for them to overflow, as is the case when used in for- and while-loops
The unchecked keyword is new in solidity version 0.8.0, so this only applies to that version or higher, which these instances are. This saves 30-40 gas per loop
There are 4 instances of this issue:
File: contracts/VotingEscrow.sol
309: for (uint256 i = 0; i < 255; i++) {
717: for (uint256 i = 0; i < 128; i++) {
739: for (uint256 i = 0; i < 128; i++) {
834: for (uint256 i = 0; i < 255; i++) {
public/external function names and public member variable names can be optimized to save gas. See this link for an example of how it works. Below are the interfaces/abstract contracts that can be optimized so that the most frequently-called functions use the least amount of gas possible during method lookup. Method IDs that have two leading zero bytes can save 128 gas each during deployment, and renaming functions to have lower method IDs will save 22 gas per call, per sorted position shifted
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
Use a solidity version of at least 0.8.4 to get custom errors, which are cheaper at deployment than revert()/require() strings
Use a solidity version of at least 0.8.10 to have external calls skip contract existence checks if the external call has a return value
[G‑16] ++i costs less gas than i++, especially when it's used in for-loops (--i/i-- too)
Saves 5 gas per loop
There are 4 instances of this issue:
File: contracts/VotingEscrow.sol
309: for (uint256 i = 0; i < 255; i++) {
717: for (uint256 i = 0; i < 128; i++) {
739: for (uint256 i = 0; i < 128; i++) {
834: for (uint256 i = 0; i < 255; i++) {
[G‑17] Usage of uints/ints smaller than 32 bytes (256 bits) incurs overhead
When using elements that are smaller than 32 bytes, your contract’s gas usage may be higher. This is because the EVM operates on 32 bytes at a time. Therefore, if the element is smaller than that, the EVM must use more operations in order to reduce the size of the element from 32 bytes to the desired size.
https://docs.soliditylang.org/en/v0.8.11/internals/layout_in_storage.html
Each operation involving a uint8 costs an extra 22-28 gas (depending on whether the other operand is also a variable of type uint8) as compared to ones involving uint256, due to the compiler having to clear the higher bits of the memory word before operating on the uint8, as well as the associated stack operations of doing so. Use a larger size then downcast where needed
There are 3 instances of this issue:
File: contracts/VotingEscrow.sol
/// @audit int128 oldSlopeDelta
380: oldSlopeDelta = oldSlopeDelta + userOldPoint.slope;
/// @audit int128 oldSlopeDelta
382: oldSlopeDelta = oldSlopeDelta - userNewPoint.slope; // It was a new deposit, not extension
/// @audit int128 newSlopeDelta
388: newSlopeDelta = newSlopeDelta - userNewPoint.slope; // old slope disappeared at this point
[G‑18] Using private rather than public for constants, saves gas
If needed, the values can be read from the verified contract source code, or if there are multiple values there can be a single getter function that returns a tuple of the values of all currently-public constants. Saves 3406-3606 gas in deployment gas due to the compiler not having to create non-payable getter functions for deployment calldata, not having to store the bytes of the value outside of where it's used, and not adding another entry to the method ID table
There are 3 instances of this issue:
File: contracts/VotingEscrow.sol
46: uint256 public constant WEEK = 7 days;
47: uint256 public constant MAXTIME = 365 days;
48: uint256 public constant MULTIPLIER = 10**18;
<x> / 2 is the same as <x> >> 1. While the compiler uses the SHR opcode to accomplish both, the version that uses division incurs an overhead of 20 gas due to JUMPs to and from a compiler utility function that introduces checks which can be avoided by using unchecked {} around the division by two
[G‑20] Stack variable used as a cheaper cache for a state variable is only used once
If the variable is only accessed once, it's cheaper to use the state variable directly that one time, and save the 3 gas the extra stack assignment would spend
[G‑21] require() or revert() statements that check input arguments should be at the top of the function
Checks that involve constants should come before checks that involve state variables, function calls, and calculations. By doing these checks first, the function is able to revert before wasting a Gcoldsload (**2100 gas***) in a function that may ultimately revert in the unhappy case.
There are 2 instances of this issue:
File: contracts/VotingEscrow.sol
/// @audit expensive op on line 410
412: require(_value > 0, "Only non zero amount");
/// @audit expensive op on line 446
448: require(_value > 0, "Only non zero amount");
Summary
Gas Optimizations
address
/ID mappings can be combined into a singlemapping
of anaddress
/ID to astruct
, where appropriateimmutable
calldata
instead ofmemory
for read-only arguments inexternal
functions saves gasstorage
instead ofmemory
for structs/arrays saves gas<x> += <y>
costs more gas than<x> = <x> + <y>
for state variablesinternal
functions only called once can be inlined to save gasunchecked {}
for subtractions where the operands cannot underflow because of a previousrequire()
orif
-statement++i
/i++
should beunchecked{++i}
/unchecked{i++}
when it is not possible for them to overflow, as is the case when used infor
- andwhile
-loopsbool
s for storage incurs overhead> 0
costs more gas than!= 0
when used on auint
in arequire()
statement++i
costs less gas thani++
, especially when it's used infor
-loops (--i
/i--
too)uints
/ints
smaller than 32 bytes (256 bits) incurs overheadprivate
rather thanpublic
for constants, saves gasrequire()
orrevert()
statements that check input arguments should be at the top of the functionrevert()
/require()
strings to save gasTotal: 114 instances over 23 issues
Gas Optimizations
[G‑01] Multiple
address
/ID mappings can be combined into a singlemapping
of anaddress
/ID to astruct
, where appropriateSaves a storage slot for the mapping. Depending on the circumstances and sizes of types, can avoid a Gsset (20000 gas) per mapping combined. Reads and subsequent writes can also be cheaper when a function requires both values and they both fit in the same storage slot. Finally, if both fields are accessed in the same function, can save ~42 gas per access due to not having to recalculate the key's keccak256 hash (Gkeccak256 - 30 gas) and that calculation's associated stack operations.
There is 1 instance of this issue:
https://github.com/code-423n4/2022-08-fiatdao/blob/fece3bdb79ccacb501099c24b60312cd0b2e4bb2/contracts/VotingEscrow.sol#L58-L59
[G‑02] State variables only set in the constructor should be declared
immutable
Avoids a Gsset (20000 gas) in the constructor, and replaces the first access in each transaction (Gcoldsload - 2100 gas) and each access thereafter (Gwarmacces - 100 gas) with a
PUSH32
(3 gas).There are 14 instances of this issue:
https://github.com/code-423n4/2022-08-fiatdao/blob/fece3bdb79ccacb501099c24b60312cd0b2e4bb2/contracts/features/Blocklist.sol#L15
https://github.com/code-423n4/2022-08-fiatdao/blob/fece3bdb79ccacb501099c24b60312cd0b2e4bb2/contracts/VotingEscrow.sol#L107
[G‑03] Structs can be packed into fewer storage slots
Each slot saved can avoid an extra Gsset (20000 gas) for the first setting of the struct. Subsequent reads as well as writes have smaller gas savings
There is 1 instance of this issue:
https://github.com/code-423n4/2022-08-fiatdao/blob/fece3bdb79ccacb501099c24b60312cd0b2e4bb2/contracts/VotingEscrow.sol#L75-L80
[G‑04] Using
calldata
instead ofmemory
for read-only arguments inexternal
functions saves gasWhen a function with a
memory
array is called externally, theabi.decode()
step has to use a for-loop to copy each index of thecalldata
to thememory
index. Each iteration of this for-loop costs at least 60 gas (i.e.60 * <mem_array>.length
). Usingcalldata
directly, obliviates the need for such a loop in the contract code and runtime execution. Note that even if an interface defines a function as havingmemory
arguments, it's still valid for implementation contracs to usecalldata
arguments instead.If the array is passed to an
internal
function which passes the array to another internal function where the array is modified and thereforememory
is used in theexternal
call, it's still more gass-efficient to usecalldata
when theexternal
function uses modifiers, since the modifiers may prevent the internal functions from being called. Structs have the same overhead as an array of length oneNote that I've also flagged instances where the function is
public
but can be marked asexternal
since it's not called by the contract, and cases where a constructor is involvedThere are 2 instances of this issue:
https://github.com/code-423n4/2022-08-fiatdao/blob/fece3bdb79ccacb501099c24b60312cd0b2e4bb2/contracts/VotingEscrow.sol#L100-L105
[G‑05] Using
storage
instead ofmemory
for structs/arrays saves gasWhen fetching data from a storage location, assigning the data to a
memory
variable causes all fields of the struct/array to be read from storage, which incurs a Gcoldsload (2100 gas) for each field of the struct/array. If the fields are read from the new memory variable, they incur an additionalMLOAD
rather than a cheap stack read. Instead of declearing the variable with thememory
keyword, declaring the variable with thestorage
keyword and caching any fields that need to be re-read in stack variables, will be much cheaper, only incuring the Gcoldsload for the fields actually read. The only time it makes sense to read the whole struct/array into amemory
variable, is if the full struct/array is being returned by the function, is being passed to a function that requiresmemory
, or if the array/struct is being read from anothermemory
array/structThere are 9 instances of this issue:
https://github.com/code-423n4/2022-08-fiatdao/blob/fece3bdb79ccacb501099c24b60312cd0b2e4bb2/contracts/VotingEscrow.sol#L410
[G‑06] Avoid contract existence checks by using solidity version 0.8.10 or later
Prior to 0.8.10 the compiler inserted extra code, including
EXTCODESIZE
(100 gas), to check for contract existence for external calls. In more recent solidity versions, the compiler will not insert these checks if the external call has a return valueThere are 3 instances of this issue:
https://github.com/code-423n4/2022-08-fiatdao/blob/fece3bdb79ccacb501099c24b60312cd0b2e4bb2/contracts/VotingEscrow.sol#L115
[G‑07] State variables should be cached in stack variables rather than re-reading them from storage
The instances below point to the second+ access of a state variable within a function. Caching of a state variable replace each Gwarmaccess (100 gas) with a much cheaper stack read. Other less obvious fixes/optimizations include having local memory caches of state variable structs, or having local caches of state variable contracts/addresses.
There are 3 instances of this issue:
https://github.com/code-423n4/2022-08-fiatdao/blob/fece3bdb79ccacb501099c24b60312cd0b2e4bb2/contracts/VotingEscrow.sol#L677
[G‑08]
<x> += <y>
costs more gas than<x> = <x> + <y>
for state variablesUsing the addition operator instead of plus-equals saves 113 gas
There is 1 instance of this issue:
https://github.com/code-423n4/2022-08-fiatdao/blob/fece3bdb79ccacb501099c24b60312cd0b2e4bb2/contracts/VotingEscrow.sol#L654
[G‑09]
internal
functions only called once can be inlined to save gasNot inlining costs 20 to 40 gas because of two extra
JUMP
instructions and additional stack operations needed for function calls.There are 3 instances of this issue:
https://github.com/code-423n4/2022-08-fiatdao/blob/fece3bdb79ccacb501099c24b60312cd0b2e4bb2/contracts/features/Blocklist.sol#L37
https://github.com/code-423n4/2022-08-fiatdao/blob/fece3bdb79ccacb501099c24b60312cd0b2e4bb2/contracts/VotingEscrow.sol#L662-L665
[G‑10] Add
unchecked {}
for subtractions where the operands cannot underflow because of a previousrequire()
orif
-statementrequire(a <= b); x = b - a
=>require(a <= b); unchecked { x = b - a }
There are 2 instances of this issue:
https://github.com/code-423n4/2022-08-fiatdao/blob/fece3bdb79ccacb501099c24b60312cd0b2e4bb2/contracts/VotingEscrow.sol#L301
[G‑11]
++i
/i++
should beunchecked{++i}
/unchecked{i++}
when it is not possible for them to overflow, as is the case when used infor
- andwhile
-loopsThe
unchecked
keyword is new in solidity version 0.8.0, so this only applies to that version or higher, which these instances are. This saves 30-40 gas per loopThere are 4 instances of this issue:
https://github.com/code-423n4/2022-08-fiatdao/blob/fece3bdb79ccacb501099c24b60312cd0b2e4bb2/contracts/VotingEscrow.sol#L309
[G‑12] Optimize names to save gas
public
/external
function names andpublic
member variable names can be optimized to save gas. See this link for an example of how it works. Below are the interfaces/abstract contracts that can be optimized so that the most frequently-called functions use the least amount of gas possible during method lookup. Method IDs that have two leading zero bytes can save 128 gas each during deployment, and renaming functions to have lower method IDs will save 22 gas per call, per sorted position shiftedThere are 4 instances of this issue:
https://github.com/code-423n4/2022-08-fiatdao/blob/fece3bdb79ccacb501099c24b60312cd0b2e4bb2/contracts/features/Blocklist.sol#L9
https://github.com/code-423n4/2022-08-fiatdao/blob/fece3bdb79ccacb501099c24b60312cd0b2e4bb2/contracts/interfaces/IBlocklist.sol#L6
https://github.com/code-423n4/2022-08-fiatdao/blob/fece3bdb79ccacb501099c24b60312cd0b2e4bb2/contracts/interfaces/IVotingEscrow.sol#L4
https://github.com/code-423n4/2022-08-fiatdao/blob/fece3bdb79ccacb501099c24b60312cd0b2e4bb2/contracts/VotingEscrow.sol#L23
[G‑13] Using
bool
s for storage incurs overheadhttps://github.com/OpenZeppelin/openzeppelin-contracts/blob/58f635312aa21f947cae5f8578638a85aa2519f5/contracts/security/ReentrancyGuard.sol#L23-L27 Use
uint256(1)
anduint256(2)
for true/false to avoid a Gwarmaccess (100 gas) for the extra SLOAD, and to avoid Gsset (20000 gas) when changing fromfalse
totrue
, after having beentrue
in the pastThere is 1 instance of this issue:
https://github.com/code-423n4/2022-08-fiatdao/blob/fece3bdb79ccacb501099c24b60312cd0b2e4bb2/contracts/features/Blocklist.sol#L10
[G‑14] Use a more recent version of solidity
Use a solidity version of at least 0.8.4 to get custom errors, which are cheaper at deployment than
revert()/require()
strings Use a solidity version of at least 0.8.10 to have external calls skip contract existence checks if the external call has a return valueThere are 5 instances of this issue:
https://github.com/code-423n4/2022-08-fiatdao/blob/fece3bdb79ccacb501099c24b60312cd0b2e4bb2/contracts/features/Blocklist.sol#L2
https://github.com/code-423n4/2022-08-fiatdao/blob/fece3bdb79ccacb501099c24b60312cd0b2e4bb2/contracts/interfaces/IBlocklist.sol#L2
https://github.com/code-423n4/2022-08-fiatdao/blob/fece3bdb79ccacb501099c24b60312cd0b2e4bb2/contracts/interfaces/IERC20.sol#L2
https://github.com/code-423n4/2022-08-fiatdao/blob/fece3bdb79ccacb501099c24b60312cd0b2e4bb2/contracts/interfaces/IVotingEscrow.sol#L2
https://github.com/code-423n4/2022-08-fiatdao/blob/fece3bdb79ccacb501099c24b60312cd0b2e4bb2/contracts/VotingEscrow.sol#L2
[G‑15] Using
> 0
costs more gas than!= 0
when used on auint
in arequire()
statementThis change saves 6 gas per instance. The optimization works until solidity version 0.8.13 where there is a regression in gas costs.
There are 2 instances of this issue:
https://github.com/code-423n4/2022-08-fiatdao/blob/fece3bdb79ccacb501099c24b60312cd0b2e4bb2/contracts/VotingEscrow.sol#L412
[G‑16]
++i
costs less gas thani++
, especially when it's used infor
-loops (--i
/i--
too)Saves 5 gas per loop
There are 4 instances of this issue:
https://github.com/code-423n4/2022-08-fiatdao/blob/fece3bdb79ccacb501099c24b60312cd0b2e4bb2/contracts/VotingEscrow.sol#L309
[G‑17] Usage of
uints
/ints
smaller than 32 bytes (256 bits) incurs overheadhttps://docs.soliditylang.org/en/v0.8.11/internals/layout_in_storage.html Each operation involving a
uint8
costs an extra 22-28 gas (depending on whether the other operand is also a variable of typeuint8
) as compared to ones involvinguint256
, due to the compiler having to clear the higher bits of the memory word before operating on theuint8
, as well as the associated stack operations of doing so. Use a larger size then downcast where neededThere are 3 instances of this issue:
https://github.com/code-423n4/2022-08-fiatdao/blob/fece3bdb79ccacb501099c24b60312cd0b2e4bb2/contracts/VotingEscrow.sol#L380
[G‑18] Using
private
rather thanpublic
for constants, saves gasIf needed, the values can be read from the verified contract source code, or if there are multiple values there can be a single getter function that returns a tuple of the values of all currently-public constants. Saves 3406-3606 gas in deployment gas due to the compiler not having to create non-payable getter functions for deployment calldata, not having to store the bytes of the value outside of where it's used, and not adding another entry to the method ID table
There are 3 instances of this issue:
https://github.com/code-423n4/2022-08-fiatdao/blob/fece3bdb79ccacb501099c24b60312cd0b2e4bb2/contracts/VotingEscrow.sol#L46
[G‑19] Division by two should use bit shifting
<x> / 2
is the same as<x> >> 1
. While the compiler uses theSHR
opcode to accomplish both, the version that uses division incurs an overhead of 20 gas due toJUMP
s to and from a compiler utility function that introduces checks which can be avoided by usingunchecked {}
around the division by twoThere are 2 instances of this issue:
https://github.com/code-423n4/2022-08-fiatdao/blob/fece3bdb79ccacb501099c24b60312cd0b2e4bb2/contracts/VotingEscrow.sol#L719
[G‑20] Stack variable used as a cheaper cache for a state variable is only used once
If the variable is only accessed once, it's cheaper to use the state variable directly that one time, and save the 3 gas the extra stack assignment would spend
There is 1 instance of this issue:
https://github.com/code-423n4/2022-08-fiatdao/blob/fece3bdb79ccacb501099c24b60312cd0b2e4bb2/contracts/VotingEscrow.sol#L865
[G‑21]
require()
orrevert()
statements that check input arguments should be at the top of the functionChecks that involve constants should come before checks that involve state variables, function calls, and calculations. By doing these checks first, the function is able to revert before wasting a Gcoldsload (**2100 gas***) in a function that may ultimately revert in the unhappy case.
There are 2 instances of this issue:
https://github.com/code-423n4/2022-08-fiatdao/blob/fece3bdb79ccacb501099c24b60312cd0b2e4bb2/contracts/VotingEscrow.sol#L412
[G‑22] Superfluous event fields
block.timestamp
andblock.number
are added to event information by default so adding them manually wastes gasThere are 2 instances of this issue:
https://github.com/code-423n4/2022-08-fiatdao/blob/fece3bdb79ccacb501099c24b60312cd0b2e4bb2/contracts/VotingEscrow.sol#L30
[G‑23] Use custom errors rather than
revert()
/require()
strings to save gasCustom errors are available from solidity version 0.8.4. Custom errors save ~50 gas each time they're hit by avoiding having to allocate and store the revert string. Not defining the strings also save deployment gas
There are 42 instances of this issue:
https://github.com/code-423n4/2022-08-fiatdao/blob/fece3bdb79ccacb501099c24b60312cd0b2e4bb2/contracts/features/Blocklist.sol#L24
https://github.com/code-423n4/2022-08-fiatdao/blob/fece3bdb79ccacb501099c24b60312cd0b2e4bb2/contracts/VotingEscrow.sol#L116