From 37c35a1eaab77caa78abeb40008cab414329a182 Mon Sep 17 00:00:00 2001 From: cairo Date: Tue, 17 Sep 2024 22:39:45 +0200 Subject: [PATCH 01/84] Update to match Solidity pragma (#5198) --- contracts/utils/cryptography/Hashes.sol | 2 +- contracts/utils/structs/MerkleTree.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/utils/cryptography/Hashes.sol b/contracts/utils/cryptography/Hashes.sol index c78bc80fd79..85efd6d294e 100644 --- a/contracts/utils/cryptography/Hashes.sol +++ b/contracts/utils/cryptography/Hashes.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.20; /** * @dev Library of standard hash functions. diff --git a/contracts/utils/structs/MerkleTree.sol b/contracts/utils/structs/MerkleTree.sol index 93f59ace585..c4933f79ef8 100644 --- a/contracts/utils/structs/MerkleTree.sol +++ b/contracts/utils/structs/MerkleTree.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.20; import {Hashes} from "../cryptography/Hashes.sol"; import {Arrays} from "../Arrays.sol"; From 809ded806fe1d952027026b056ff6623ff2e2dfc Mon Sep 17 00:00:00 2001 From: cairo Date: Tue, 17 Sep 2024 22:42:06 +0200 Subject: [PATCH 02/84] Remove redundant modulo operation in P256 (#5200) --- contracts/utils/cryptography/P256.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/utils/cryptography/P256.sol b/contracts/utils/cryptography/P256.sol index 69717bdc51a..1b488c180be 100644 --- a/contracts/utils/cryptography/P256.sol +++ b/contracts/utils/cryptography/P256.sol @@ -130,7 +130,7 @@ library P256 { uint256 ry2 = addmod(mulmod(addmod(mulmod(rx, rx, p), A, p), rx, p), B, p); // weierstrass equation y² = x³ + a.x + b uint256 ry = Math.modExp(ry2, P1DIV4, p); // This formula for sqrt work because P ≡ 3 (mod 4) if (mulmod(ry, ry, p) != ry2) return (0, 0); // Sanity check - if (ry % 2 != v % 2) ry = p - ry; + if (ry % 2 != v) ry = p - ry; JPoint[16] memory points = _preComputeJacobianPoints(rx, ry); uint256 w = Math.invModPrime(uint256(r), N); From 3f901696f77997c060cc4c43fb77bfbd104713f3 Mon Sep 17 00:00:00 2001 From: cairo Date: Wed, 18 Sep 2024 18:21:17 +0200 Subject: [PATCH 03/84] Clean dirty addresses and booleans (#5195) Co-authored-by: Hadrien Croubois --- contracts/utils/SlotDerivation.sol | 4 ++-- scripts/generate/helpers/sanitize.js | 5 +++++ scripts/generate/templates/Packing.js | 9 +++++---- scripts/generate/templates/Slot.opts.js | 2 ++ scripts/generate/templates/SlotDerivation.js | 3 ++- .../generate/templates/SlotDerivation.t.js | 14 +++++++++++++ test/utils/SlotDerivation.t.sol | 20 +++++++++++++++++++ 7 files changed, 50 insertions(+), 7 deletions(-) create mode 100644 scripts/generate/helpers/sanitize.js diff --git a/contracts/utils/SlotDerivation.sol b/contracts/utils/SlotDerivation.sol index 62c28a55fa6..c248edc01c1 100644 --- a/contracts/utils/SlotDerivation.sol +++ b/contracts/utils/SlotDerivation.sol @@ -70,7 +70,7 @@ library SlotDerivation { */ function deriveMapping(bytes32 slot, address key) internal pure returns (bytes32 result) { assembly ("memory-safe") { - mstore(0x00, key) + mstore(0x00, and(key, shr(96, not(0)))) mstore(0x20, slot) result := keccak256(0x00, 0x40) } @@ -81,7 +81,7 @@ library SlotDerivation { */ function deriveMapping(bytes32 slot, bool key) internal pure returns (bytes32 result) { assembly ("memory-safe") { - mstore(0x00, key) + mstore(0x00, iszero(iszero(key))) mstore(0x20, slot) result := keccak256(0x00, 0x40) } diff --git a/scripts/generate/helpers/sanitize.js b/scripts/generate/helpers/sanitize.js new file mode 100644 index 00000000000..e680ec1bf46 --- /dev/null +++ b/scripts/generate/helpers/sanitize.js @@ -0,0 +1,5 @@ +module.exports = { + address: expr => `and(${expr}, shr(96, not(0)))`, + bool: expr => `iszero(iszero(${expr}))`, + bytes: (expr, size) => `and(${expr}, shl(${256 - 8 * size}, not(0)))`, +}; diff --git a/scripts/generate/templates/Packing.js b/scripts/generate/templates/Packing.js index b9422ef4833..d841c2f816a 100644 --- a/scripts/generate/templates/Packing.js +++ b/scripts/generate/templates/Packing.js @@ -1,4 +1,5 @@ const format = require('../format-lines'); +const sanitize = require('../helpers/sanitize'); const { product } = require('../../helpers'); const { SIZES } = require('./Packing.opts'); @@ -44,8 +45,8 @@ function pack_${left}_${right}(bytes${left} left, bytes${right} right) internal left + right } result) { assembly ("memory-safe") { - left := and(left, shl(${256 - 8 * left}, not(0))) - right := and(right, shl(${256 - 8 * right}, not(0))) + left := ${sanitize.bytes('left', left)} + right := ${sanitize.bytes('right', right)} result := or(left, shr(${8 * left}, right)) } } @@ -55,7 +56,7 @@ const extract = (outer, inner) => `\ function extract_${outer}_${inner}(bytes${outer} self, uint8 offset) internal pure returns (bytes${inner} result) { if (offset > ${outer - inner}) revert OutOfRangeAccess(); assembly ("memory-safe") { - result := and(shl(mul(8, offset), self), shl(${256 - 8 * inner}, not(0))) + result := ${sanitize.bytes('shl(mul(8, offset), self)', inner)} } } `; @@ -64,7 +65,7 @@ const replace = (outer, inner) => `\ function replace_${outer}_${inner}(bytes${outer} self, bytes${inner} value, uint8 offset) internal pure returns (bytes${outer} result) { bytes${inner} oldValue = extract_${outer}_${inner}(self, offset); assembly ("memory-safe") { - value := and(value, shl(${256 - 8 * inner}, not(0))) + value := ${sanitize.bytes('value', inner)} result := xor(self, shr(mul(8, offset), xor(oldValue, value))) } } diff --git a/scripts/generate/templates/Slot.opts.js b/scripts/generate/templates/Slot.opts.js index aed1f988845..3eca2bcf08f 100644 --- a/scripts/generate/templates/Slot.opts.js +++ b/scripts/generate/templates/Slot.opts.js @@ -10,4 +10,6 @@ const TYPES = [ { type: 'bytes', isValueType: false }, ].map(type => Object.assign(type, { name: type.name ?? capitalize(type.type) })); +Object.assign(TYPES, Object.fromEntries(TYPES.map(entry => [entry.type, entry]))); + module.exports = { TYPES }; diff --git a/scripts/generate/templates/SlotDerivation.js b/scripts/generate/templates/SlotDerivation.js index 5311fb3c8d1..39d0d9e3508 100644 --- a/scripts/generate/templates/SlotDerivation.js +++ b/scripts/generate/templates/SlotDerivation.js @@ -1,4 +1,5 @@ const format = require('../format-lines'); +const sanitize = require('../helpers/sanitize'); const { TYPES } = require('./Slot.opts'); const header = `\ @@ -77,7 +78,7 @@ const mapping = ({ type }) => `\ */ function deriveMapping(bytes32 slot, ${type} key) internal pure returns (bytes32 result) { assembly ("memory-safe") { - mstore(0x00, key) + mstore(0x00, ${(sanitize[type] ?? (x => x))('key')}) mstore(0x20, slot) result := keccak256(0x00, 0x40) } diff --git a/scripts/generate/templates/SlotDerivation.t.js b/scripts/generate/templates/SlotDerivation.t.js index dc7b07ff45a..f03e1fc2598 100644 --- a/scripts/generate/templates/SlotDerivation.t.js +++ b/scripts/generate/templates/SlotDerivation.t.js @@ -61,6 +61,18 @@ function testSymbolicDeriveMapping${name}(${type} key) public { } `; +const mappingDirty = ({ type, name }) => `\ +function testSymbolicDeriveMapping${name}Dirty(bytes32 dirtyKey) public { + ${type} key; + assembly { + key := dirtyKey + } + + // run the "normal" test using a potentially dirty value + testSymbolicDeriveMapping${name}(key); +} +`; + const boundedMapping = ({ type, name }) => `\ mapping(${type} => bytes) private _${type}Mapping; @@ -107,6 +119,8 @@ module.exports = format( })), ), ).map(type => (type.isValueType ? mapping(type) : boundedMapping(type))), + mappingDirty(TYPES.bool), + mappingDirty(TYPES.address), ), ).trimEnd(), '}', diff --git a/test/utils/SlotDerivation.t.sol b/test/utils/SlotDerivation.t.sol index 300a58b5f14..4021f0f8796 100644 --- a/test/utils/SlotDerivation.t.sol +++ b/test/utils/SlotDerivation.t.sol @@ -225,4 +225,24 @@ contract SlotDerivationTest is Test, SymTest { assertEq(baseSlot.deriveMapping(key), derivedSlot); } + + function testSymbolicDeriveMappingBooleanDirty(bytes32 dirtyKey) public { + bool key; + assembly { + key := dirtyKey + } + + // run the "normal" test using a potentially dirty value + testSymbolicDeriveMappingBoolean(key); + } + + function testSymbolicDeriveMappingAddressDirty(bytes32 dirtyKey) public { + address key; + assembly { + key := dirtyKey + } + + // run the "normal" test using a potentially dirty value + testSymbolicDeriveMappingAddress(key); + } } From f20981528fa06e88ae97c9f599b9a313ad8186ce Mon Sep 17 00:00:00 2001 From: TechVoyagerX <164723159+techvoyagerX@users.noreply.github.com> Date: Thu, 19 Sep 2024 20:18:31 +0800 Subject: [PATCH 04/84] refactor: enhance ERC6372 behavior test with detailed checks (#5164) --- test/governance/utils/ERC6372.behavior.js | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/test/governance/utils/ERC6372.behavior.js b/test/governance/utils/ERC6372.behavior.js index abcae43c7f3..32f27b59a7c 100644 --- a/test/governance/utils/ERC6372.behavior.js +++ b/test/governance/utils/ERC6372.behavior.js @@ -1,21 +1,24 @@ const { expect } = require('chai'); - const time = require('../../helpers/time'); function shouldBehaveLikeERC6372(mode = 'blocknumber') { - describe('should implement ERC-6372', function () { + describe(`ERC-6372 behavior in ${mode} mode`, function () { beforeEach(async function () { this.mock = this.mock ?? this.token ?? this.votes; }); - it('clock is correct', async function () { - expect(await this.mock.clock()).to.equal(await time.clock[mode]()); + it('should have a correct clock value', async function () { + const currentClock = await this.mock.clock(); + const expectedClock = await time.clock[mode](); + expect(currentClock).to.equal(expectedClock, `Clock mismatch in ${mode} mode`); }); - it('CLOCK_MODE is correct', async function () { - const params = new URLSearchParams(await this.mock.CLOCK_MODE()); - expect(params.get('mode')).to.equal(mode); - expect(params.get('from')).to.equal(mode == 'blocknumber' ? 'default' : null); + it('should have the correct CLOCK_MODE parameters', async function () { + const clockModeParams = new URLSearchParams(await this.mock.CLOCK_MODE()); + const expectedFromValue = mode === 'blocknumber' ? 'default' : null; + + expect(clockModeParams.get('mode')).to.equal(mode, `Expected mode to be ${mode}`); + expect(clockModeParams.get('from')).to.equal(expectedFromValue, `Expected 'from' to be ${expectedFromValue}`); }); }); } From 3cfebcb5c4fc4730bad4add1213ca52b85472dfa Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 19 Sep 2024 14:29:39 +0200 Subject: [PATCH 05/84] Refactor Heap.sol to remove `index` and `lookup` (#5190) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernesto García --- contracts/utils/structs/Heap.sol | 467 ++++-------------------- scripts/generate/run.js | 2 - scripts/generate/templates/Heap.js | 327 ----------------- scripts/generate/templates/Heap.opts.js | 13 - scripts/generate/templates/Heap.t.js | 89 ----- test/utils/structs/Heap.t.sol | 83 +---- test/utils/structs/Heap.test.js | 190 +++++----- 7 files changed, 159 insertions(+), 1012 deletions(-) delete mode 100644 scripts/generate/templates/Heap.js delete mode 100644 scripts/generate/templates/Heap.opts.js delete mode 100644 scripts/generate/templates/Heap.t.js diff --git a/contracts/utils/structs/Heap.sol b/contracts/utils/structs/Heap.sol index 24d97292fdf..c8e75f240a1 100644 --- a/contracts/utils/structs/Heap.sol +++ b/contracts/utils/structs/Heap.sol @@ -1,33 +1,25 @@ // SPDX-License-Identifier: MIT -// This file was procedurally generated from scripts/generate/templates/Heap.js. pragma solidity ^0.8.20; import {Math} from "../math/Math.sol"; import {SafeCast} from "../math/SafeCast.sol"; import {Comparators} from "../Comparators.sol"; +import {Arrays} from "../Arrays.sol"; import {Panic} from "../Panic.sol"; +import {StorageSlot} from "../StorageSlot.sol"; /** * @dev Library for managing https://en.wikipedia.org/wiki/Binary_heap[binary heap] that can be used as * https://en.wikipedia.org/wiki/Priority_queue[priority queue]. * - * Heaps are represented as an array of Node objects. This array stores two overlapping structures: - * * A tree structure where the first element (index 0) is the root, and where the node at index i is the child of the - * node at index (i-1)/2 and the father of nodes at index 2*i+1 and 2*i+2. Each node stores the index (in the array) - * where the corresponding value is stored. - * * A list of payloads values where each index contains a value and a lookup index. The type of the value depends on - * the variant being used. The lookup is the index of the node (in the tree) that points to this value. - * - * Some invariants: - * ``` - * i == heap.data[heap.data[i].index].lookup // for all indices i - * i == heap.data[heap.data[i].lookup].index // for all indices i - * ``` + * Heaps are represented as an tree of values where the first element (index 0) is the root, and where the node at + * index i is the child of the node at index (i-1)/2 and the father of nodes at index 2*i+1 and 2*i+2. Each node + * stores an element of the heap. * * The structure is ordered so that each node is bigger than its parent. An immediate consequence is that the * highest priority value is the one at the root. This value can be looked up in constant time (O(1)) at - * `heap.data[heap.data[0].index].value` + * `heap.tree[0].value` * * The structure is designed to perform the following operations with the corresponding complexities: * @@ -37,8 +29,13 @@ import {Panic} from "../Panic.sol"; * * replace (replace the highest priority value with a new value): O(log(n)) * * length (get the number of elements): O(1) * * clear (remove all elements): O(1) + * + * IMPORTANT: This library allows for the use of custom comparator functions. Given that manipulating + * memory can lead to unexpected behavior. Consider verifying that the comparator does not manipulate + * the Heap's state directly and that it follows the Solidity memory safety rules. */ library Heap { + using Arrays for *; using Math for *; using SafeCast for *; @@ -48,24 +45,15 @@ library Heap { * Each element of that structure uses 2 storage slots. */ struct Uint256Heap { - Uint256HeapNode[] data; - } - - /** - * @dev Internal node type for Uint256Heap. Stores a value of type uint256. - */ - struct Uint256HeapNode { - uint256 value; - uint64 index; // position -> value - uint64 lookup; // value -> position + uint256[] tree; } /** * @dev Lookup the root element of the heap. */ function peek(Uint256Heap storage self) internal view returns (uint256) { - // self.data[0] will `ARRAY_ACCESS_OUT_OF_BOUNDS` panic if heap is empty. - return _unsafeNodeAccess(self, self.data[0].index).value; + // self.tree[0] will `ARRAY_ACCESS_OUT_OF_BOUNDS` panic if heap is empty. + return self.tree[0]; } /** @@ -89,44 +77,19 @@ library Heap { function(uint256, uint256) view returns (bool) comp ) internal returns (uint256) { unchecked { - uint64 size = length(self); + uint256 size = length(self); if (size == 0) Panic.panic(Panic.EMPTY_ARRAY_POP); - uint64 last = size - 1; - - // get root location (in the data array) and value - Uint256HeapNode storage rootNode = _unsafeNodeAccess(self, 0); - uint64 rootIdx = rootNode.index; - Uint256HeapNode storage rootData = _unsafeNodeAccess(self, rootIdx); - Uint256HeapNode storage lastNode = _unsafeNodeAccess(self, last); - uint256 rootDataValue = rootData.value; - - // if root is not the last element of the data array (that will get popped), reorder the data array. - if (rootIdx != last) { - // get details about the value stored in the last element of the array (that will get popped) - uint64 lastDataIdx = lastNode.lookup; - uint256 lastDataValue = lastNode.value; - // copy these values to the location of the root (that is safe, and that we no longer use) - rootData.value = lastDataValue; - rootData.lookup = lastDataIdx; - // update the tree node that used to point to that last element (value now located where the root was) - _unsafeNodeAccess(self, lastDataIdx).index = rootIdx; - } - - // get last leaf location (in the data array) and value - uint64 lastIdx = lastNode.index; - uint256 lastValue = _unsafeNodeAccess(self, lastIdx).value; - - // move the last leaf to the root, pop last leaf ... - rootNode.index = lastIdx; - _unsafeNodeAccess(self, lastIdx).lookup = 0; - self.data.pop(); + // cache + uint256 rootValue = self.tree.unsafeAccess(0).value; + uint256 lastValue = self.tree.unsafeAccess(size - 1).value; - // ... and heapify - _siftDown(self, last, 0, lastValue, comp); + // swap last leaf with root, shrink tree and re-heapify + self.tree.pop(); + self.tree.unsafeAccess(0).value = lastValue; + _siftDown(self, size - 1, 0, lastValue, comp); - // return root value - return rootDataValue; + return rootValue; } } @@ -151,10 +114,10 @@ library Heap { uint256 value, function(uint256, uint256) view returns (bool) comp ) internal { - uint64 size = length(self); - if (size == type(uint64).max) Panic.panic(Panic.RESOURCE_ERROR); + uint256 size = length(self); - self.data.push(Uint256HeapNode({index: size, lookup: size, value: value})); + // push new item and re-heapify + self.tree.push(value); _siftUp(self, size, value, comp); } @@ -181,396 +144,108 @@ library Heap { uint256 newValue, function(uint256, uint256) view returns (bool) comp ) internal returns (uint256) { - uint64 size = length(self); + uint256 size = length(self); if (size == 0) Panic.panic(Panic.EMPTY_ARRAY_POP); - // position of the node that holds the data for the root - uint64 rootIdx = _unsafeNodeAccess(self, 0).index; - // storage pointer to the node that holds the data for the root - Uint256HeapNode storage rootData = _unsafeNodeAccess(self, rootIdx); + // cache + uint256 oldValue = self.tree.unsafeAccess(0).value; - // cache old value and replace it - uint256 oldValue = rootData.value; - rootData.value = newValue; - - // re-heapify + // replace and re-heapify + self.tree.unsafeAccess(0).value = newValue; _siftDown(self, size, 0, newValue, comp); - // return old root value return oldValue; } /** * @dev Returns the number of elements in the heap. */ - function length(Uint256Heap storage self) internal view returns (uint64) { - return self.data.length.toUint64(); + function length(Uint256Heap storage self) internal view returns (uint256) { + return self.tree.length; } /** * @dev Removes all elements in the heap. */ function clear(Uint256Heap storage self) internal { - Uint256HeapNode[] storage data = self.data; - assembly ("memory-safe") { - sstore(data.slot, 0) - } + self.tree.unsafeSetLength(0); } /** * @dev Swap node `i` and `j` in the tree. */ - function _swap(Uint256Heap storage self, uint64 i, uint64 j) private { - Uint256HeapNode storage ni = _unsafeNodeAccess(self, i); - Uint256HeapNode storage nj = _unsafeNodeAccess(self, j); - uint64 ii = ni.index; - uint64 jj = nj.index; - // update pointers to the data (swap the value) - ni.index = jj; - nj.index = ii; - // update lookup pointers for consistency - _unsafeNodeAccess(self, ii).lookup = j; - _unsafeNodeAccess(self, jj).lookup = i; + function _swap(Uint256Heap storage self, uint256 i, uint256 j) private { + StorageSlot.Uint256Slot storage ni = self.tree.unsafeAccess(i); + StorageSlot.Uint256Slot storage nj = self.tree.unsafeAccess(j); + (ni.value, nj.value) = (nj.value, ni.value); } /** - * @dev Perform heap maintenance on `self`, starting at position `pos` (with the `value`), using `comp` as a + * @dev Perform heap maintenance on `self`, starting at `index` (with the `value`), using `comp` as a * comparator, and moving toward the leaves of the underlying tree. * * NOTE: This is a private function that is called in a trusted context with already cached parameters. `length` - * and `value` could be extracted from `self` and `pos`, but that would require redundant storage read. These + * and `value` could be extracted from `self` and `index`, but that would require redundant storage read. These * parameters are not verified. It is the caller role to make sure the parameters are correct. */ function _siftDown( Uint256Heap storage self, - uint64 size, - uint64 pos, + uint256 size, + uint256 index, uint256 value, function(uint256, uint256) view returns (bool) comp ) private { - uint256 left = 2 * pos + 1; // this could overflow uint64 - uint256 right = 2 * pos + 2; // this could overflow uint64 - - if (right < size) { - // the check guarantees that `left` and `right` are both valid uint64 - uint64 lIndex = uint64(left); - uint64 rIndex = uint64(right); - uint256 lValue = _unsafeNodeAccess(self, _unsafeNodeAccess(self, lIndex).index).value; - uint256 rValue = _unsafeNodeAccess(self, _unsafeNodeAccess(self, rIndex).index).value; + // Check if there is a risk of overflow when computing the indices of the child nodes. If that is the case, + // there cannot be child nodes in the tree, so sifting is done. + if (index >= type(uint256).max / 2) return; + + // Compute the indices of the potential child nodes + uint256 lIndex = 2 * index + 1; + uint256 rIndex = 2 * index + 2; + + // Three cases: + // 1. Both children exist: sifting may continue on one of the branch (selection required) + // 2. Only left child exist: sifting may contineu on the left branch (no selection required) + // 3. Neither child exist: sifting is done + if (rIndex < size) { + uint256 lValue = self.tree.unsafeAccess(lIndex).value; + uint256 rValue = self.tree.unsafeAccess(rIndex).value; if (comp(lValue, value) || comp(rValue, value)) { - uint64 index = uint64(comp(lValue, rValue).ternary(lIndex, rIndex)); - _swap(self, pos, index); - _siftDown(self, size, index, value, comp); + uint256 cIndex = comp(lValue, rValue).ternary(lIndex, rIndex); + _swap(self, index, cIndex); + _siftDown(self, size, cIndex, value, comp); } - } else if (left < size) { - // the check guarantees that `left` is a valid uint64 - uint64 lIndex = uint64(left); - uint256 lValue = _unsafeNodeAccess(self, _unsafeNodeAccess(self, lIndex).index).value; + } else if (lIndex < size) { + uint256 lValue = self.tree.unsafeAccess(lIndex).value; if (comp(lValue, value)) { - _swap(self, pos, lIndex); + _swap(self, index, lIndex); _siftDown(self, size, lIndex, value, comp); } } } /** - * @dev Perform heap maintenance on `self`, starting at position `pos` (with the `value`), using `comp` as a + * @dev Perform heap maintenance on `self`, starting at `index` (with the `value`), using `comp` as a * comparator, and moving toward the root of the underlying tree. * * NOTE: This is a private function that is called in a trusted context with already cached parameters. `value` - * could be extracted from `self` and `pos`, but that would require redundant storage read. These parameters are not + * could be extracted from `self` and `index`, but that would require redundant storage read. These parameters are not * verified. It is the caller role to make sure the parameters are correct. */ function _siftUp( Uint256Heap storage self, - uint64 pos, + uint256 index, uint256 value, function(uint256, uint256) view returns (bool) comp ) private { unchecked { - while (pos > 0) { - uint64 parent = (pos - 1) / 2; - uint256 parentValue = _unsafeNodeAccess(self, _unsafeNodeAccess(self, parent).index).value; - if (comp(parentValue, value)) break; - _swap(self, pos, parent); - pos = parent; - } - } - } - - function _unsafeNodeAccess( - Uint256Heap storage self, - uint64 pos - ) private pure returns (Uint256HeapNode storage result) { - assembly ("memory-safe") { - mstore(0x00, self.slot) - result.slot := add(keccak256(0x00, 0x20), mul(pos, 2)) - } - } - - /** - * @dev Binary heap that supports values of type uint208. - * - * Each element of that structure uses 1 storage slots. - */ - struct Uint208Heap { - Uint208HeapNode[] data; - } - - /** - * @dev Internal node type for Uint208Heap. Stores a value of type uint208. - */ - struct Uint208HeapNode { - uint208 value; - uint24 index; // position -> value - uint24 lookup; // value -> position - } - - /** - * @dev Lookup the root element of the heap. - */ - function peek(Uint208Heap storage self) internal view returns (uint208) { - // self.data[0] will `ARRAY_ACCESS_OUT_OF_BOUNDS` panic if heap is empty. - return _unsafeNodeAccess(self, self.data[0].index).value; - } - - /** - * @dev Remove (and return) the root element for the heap using the default comparator. - * - * NOTE: All inserting and removal from a heap should always be done using the same comparator. Mixing comparator - * during the lifecycle of a heap will result in undefined behavior. - */ - function pop(Uint208Heap storage self) internal returns (uint208) { - return pop(self, Comparators.lt); - } - - /** - * @dev Remove (and return) the root element for the heap using the provided comparator. - * - * NOTE: All inserting and removal from a heap should always be done using the same comparator. Mixing comparator - * during the lifecycle of a heap will result in undefined behavior. - */ - function pop( - Uint208Heap storage self, - function(uint256, uint256) view returns (bool) comp - ) internal returns (uint208) { - unchecked { - uint24 size = length(self); - if (size == 0) Panic.panic(Panic.EMPTY_ARRAY_POP); - - uint24 last = size - 1; - - // get root location (in the data array) and value - Uint208HeapNode storage rootNode = _unsafeNodeAccess(self, 0); - uint24 rootIdx = rootNode.index; - Uint208HeapNode storage rootData = _unsafeNodeAccess(self, rootIdx); - Uint208HeapNode storage lastNode = _unsafeNodeAccess(self, last); - uint208 rootDataValue = rootData.value; - - // if root is not the last element of the data array (that will get popped), reorder the data array. - if (rootIdx != last) { - // get details about the value stored in the last element of the array (that will get popped) - uint24 lastDataIdx = lastNode.lookup; - uint208 lastDataValue = lastNode.value; - // copy these values to the location of the root (that is safe, and that we no longer use) - rootData.value = lastDataValue; - rootData.lookup = lastDataIdx; - // update the tree node that used to point to that last element (value now located where the root was) - _unsafeNodeAccess(self, lastDataIdx).index = rootIdx; - } - - // get last leaf location (in the data array) and value - uint24 lastIdx = lastNode.index; - uint208 lastValue = _unsafeNodeAccess(self, lastIdx).value; - - // move the last leaf to the root, pop last leaf ... - rootNode.index = lastIdx; - _unsafeNodeAccess(self, lastIdx).lookup = 0; - self.data.pop(); - - // ... and heapify - _siftDown(self, last, 0, lastValue, comp); - - // return root value - return rootDataValue; - } - } - - /** - * @dev Insert a new element in the heap using the default comparator. - * - * NOTE: All inserting and removal from a heap should always be done using the same comparator. Mixing comparator - * during the lifecycle of a heap will result in undefined behavior. - */ - function insert(Uint208Heap storage self, uint208 value) internal { - insert(self, value, Comparators.lt); - } - - /** - * @dev Insert a new element in the heap using the provided comparator. - * - * NOTE: All inserting and removal from a heap should always be done using the same comparator. Mixing comparator - * during the lifecycle of a heap will result in undefined behavior. - */ - function insert( - Uint208Heap storage self, - uint208 value, - function(uint256, uint256) view returns (bool) comp - ) internal { - uint24 size = length(self); - if (size == type(uint24).max) Panic.panic(Panic.RESOURCE_ERROR); - - self.data.push(Uint208HeapNode({index: size, lookup: size, value: value})); - _siftUp(self, size, value, comp); - } - - /** - * @dev Return the root element for the heap, and replace it with a new value, using the default comparator. - * This is equivalent to using {pop} and {insert}, but requires only one rebalancing operation. - * - * NOTE: All inserting and removal from a heap should always be done using the same comparator. Mixing comparator - * during the lifecycle of a heap will result in undefined behavior. - */ - function replace(Uint208Heap storage self, uint208 newValue) internal returns (uint208) { - return replace(self, newValue, Comparators.lt); - } - - /** - * @dev Return the root element for the heap, and replace it with a new value, using the provided comparator. - * This is equivalent to using {pop} and {insert}, but requires only one rebalancing operation. - * - * NOTE: All inserting and removal from a heap should always be done using the same comparator. Mixing comparator - * during the lifecycle of a heap will result in undefined behavior. - */ - function replace( - Uint208Heap storage self, - uint208 newValue, - function(uint256, uint256) view returns (bool) comp - ) internal returns (uint208) { - uint24 size = length(self); - if (size == 0) Panic.panic(Panic.EMPTY_ARRAY_POP); - - // position of the node that holds the data for the root - uint24 rootIdx = _unsafeNodeAccess(self, 0).index; - // storage pointer to the node that holds the data for the root - Uint208HeapNode storage rootData = _unsafeNodeAccess(self, rootIdx); - - // cache old value and replace it - uint208 oldValue = rootData.value; - rootData.value = newValue; - - // re-heapify - _siftDown(self, size, 0, newValue, comp); - - // return old root value - return oldValue; - } - - /** - * @dev Returns the number of elements in the heap. - */ - function length(Uint208Heap storage self) internal view returns (uint24) { - return self.data.length.toUint24(); - } - - /** - * @dev Removes all elements in the heap. - */ - function clear(Uint208Heap storage self) internal { - Uint208HeapNode[] storage data = self.data; - assembly ("memory-safe") { - sstore(data.slot, 0) - } - } - - /** - * @dev Swap node `i` and `j` in the tree. - */ - function _swap(Uint208Heap storage self, uint24 i, uint24 j) private { - Uint208HeapNode storage ni = _unsafeNodeAccess(self, i); - Uint208HeapNode storage nj = _unsafeNodeAccess(self, j); - uint24 ii = ni.index; - uint24 jj = nj.index; - // update pointers to the data (swap the value) - ni.index = jj; - nj.index = ii; - // update lookup pointers for consistency - _unsafeNodeAccess(self, ii).lookup = j; - _unsafeNodeAccess(self, jj).lookup = i; - } - - /** - * @dev Perform heap maintenance on `self`, starting at position `pos` (with the `value`), using `comp` as a - * comparator, and moving toward the leaves of the underlying tree. - * - * NOTE: This is a private function that is called in a trusted context with already cached parameters. `length` - * and `value` could be extracted from `self` and `pos`, but that would require redundant storage read. These - * parameters are not verified. It is the caller role to make sure the parameters are correct. - */ - function _siftDown( - Uint208Heap storage self, - uint24 size, - uint24 pos, - uint208 value, - function(uint256, uint256) view returns (bool) comp - ) private { - uint256 left = 2 * pos + 1; // this could overflow uint24 - uint256 right = 2 * pos + 2; // this could overflow uint24 - - if (right < size) { - // the check guarantees that `left` and `right` are both valid uint24 - uint24 lIndex = uint24(left); - uint24 rIndex = uint24(right); - uint208 lValue = _unsafeNodeAccess(self, _unsafeNodeAccess(self, lIndex).index).value; - uint208 rValue = _unsafeNodeAccess(self, _unsafeNodeAccess(self, rIndex).index).value; - if (comp(lValue, value) || comp(rValue, value)) { - uint24 index = uint24(comp(lValue, rValue).ternary(lIndex, rIndex)); - _swap(self, pos, index); - _siftDown(self, size, index, value, comp); - } - } else if (left < size) { - // the check guarantees that `left` is a valid uint24 - uint24 lIndex = uint24(left); - uint208 lValue = _unsafeNodeAccess(self, _unsafeNodeAccess(self, lIndex).index).value; - if (comp(lValue, value)) { - _swap(self, pos, lIndex); - _siftDown(self, size, lIndex, value, comp); - } - } - } - - /** - * @dev Perform heap maintenance on `self`, starting at position `pos` (with the `value`), using `comp` as a - * comparator, and moving toward the root of the underlying tree. - * - * NOTE: This is a private function that is called in a trusted context with already cached parameters. `value` - * could be extracted from `self` and `pos`, but that would require redundant storage read. These parameters are not - * verified. It is the caller role to make sure the parameters are correct. - */ - function _siftUp( - Uint208Heap storage self, - uint24 pos, - uint208 value, - function(uint256, uint256) view returns (bool) comp - ) private { - unchecked { - while (pos > 0) { - uint24 parent = (pos - 1) / 2; - uint208 parentValue = _unsafeNodeAccess(self, _unsafeNodeAccess(self, parent).index).value; + while (index > 0) { + uint256 parentIndex = (index - 1) / 2; + uint256 parentValue = self.tree.unsafeAccess(parentIndex).value; if (comp(parentValue, value)) break; - _swap(self, pos, parent); - pos = parent; + _swap(self, index, parentIndex); + index = parentIndex; } } } - - function _unsafeNodeAccess( - Uint208Heap storage self, - uint24 pos - ) private pure returns (Uint208HeapNode storage result) { - assembly ("memory-safe") { - mstore(0x00, self.slot) - result.slot := add(keccak256(0x00, 0x20), pos) - } - } } diff --git a/scripts/generate/run.js b/scripts/generate/run.js index d49105c5a85..c28d1175b12 100755 --- a/scripts/generate/run.js +++ b/scripts/generate/run.js @@ -37,7 +37,6 @@ for (const [file, template] of Object.entries({ 'utils/structs/Checkpoints.sol': './templates/Checkpoints.js', 'utils/structs/EnumerableSet.sol': './templates/EnumerableSet.js', 'utils/structs/EnumerableMap.sol': './templates/EnumerableMap.js', - 'utils/structs/Heap.sol': './templates/Heap.js', 'utils/SlotDerivation.sol': './templates/SlotDerivation.js', 'utils/StorageSlot.sol': './templates/StorageSlot.js', 'utils/Arrays.sol': './templates/Arrays.js', @@ -50,7 +49,6 @@ for (const [file, template] of Object.entries({ // Tests for (const [file, template] of Object.entries({ 'utils/structs/Checkpoints.t.sol': './templates/Checkpoints.t.js', - 'utils/structs/Heap.t.sol': './templates/Heap.t.js', 'utils/Packing.t.sol': './templates/Packing.t.js', 'utils/SlotDerivation.t.sol': './templates/SlotDerivation.t.js', })) { diff --git a/scripts/generate/templates/Heap.js b/scripts/generate/templates/Heap.js deleted file mode 100644 index aebc42e9ed6..00000000000 --- a/scripts/generate/templates/Heap.js +++ /dev/null @@ -1,327 +0,0 @@ -const format = require('../format-lines'); -const { TYPES } = require('./Heap.opts'); -const { capitalize } = require('../../helpers'); - -/* eslint-disable max-len */ -const header = `\ -pragma solidity ^0.8.20; - -import {Math} from "../math/Math.sol"; -import {SafeCast} from "../math/SafeCast.sol"; -import {Comparators} from "../Comparators.sol"; -import {Panic} from "../Panic.sol"; - -/** - * @dev Library for managing https://en.wikipedia.org/wiki/Binary_heap[binary heap] that can be used as - * https://en.wikipedia.org/wiki/Priority_queue[priority queue]. - * - * Heaps are represented as an array of Node objects. This array stores two overlapping structures: - * * A tree structure where the first element (index 0) is the root, and where the node at index i is the child of the - * node at index (i-1)/2 and the father of nodes at index 2*i+1 and 2*i+2. Each node stores the index (in the array) - * where the corresponding value is stored. - * * A list of payloads values where each index contains a value and a lookup index. The type of the value depends on - * the variant being used. The lookup is the index of the node (in the tree) that points to this value. - * - * Some invariants: - * \`\`\` - * i == heap.data[heap.data[i].index].lookup // for all indices i - * i == heap.data[heap.data[i].lookup].index // for all indices i - * \`\`\` - * - * The structure is ordered so that each node is bigger than its parent. An immediate consequence is that the - * highest priority value is the one at the root. This value can be looked up in constant time (O(1)) at - * \`heap.data[heap.data[0].index].value\` - * - * The structure is designed to perform the following operations with the corresponding complexities: - * - * * peek (get the highest priority value): O(1) - * * insert (insert a value): O(log(n)) - * * pop (remove the highest priority value): O(log(n)) - * * replace (replace the highest priority value with a new value): O(log(n)) - * * length (get the number of elements): O(1) - * * clear (remove all elements): O(1) - */ -`; - -const generate = ({ struct, node, valueType, indexType, blockSize }) => `\ -/** - * @dev Binary heap that supports values of type ${valueType}. - * - * Each element of that structure uses ${blockSize} storage slots. - */ -struct ${struct} { - ${node}[] data; -} - -/** - * @dev Internal node type for ${struct}. Stores a value of type ${valueType}. - */ -struct ${node} { - ${valueType} value; - ${indexType} index; // position -> value - ${indexType} lookup; // value -> position -} - -/** - * @dev Lookup the root element of the heap. - */ -function peek(${struct} storage self) internal view returns (${valueType}) { - // self.data[0] will \`ARRAY_ACCESS_OUT_OF_BOUNDS\` panic if heap is empty. - return _unsafeNodeAccess(self, self.data[0].index).value; -} - -/** - * @dev Remove (and return) the root element for the heap using the default comparator. - * - * NOTE: All inserting and removal from a heap should always be done using the same comparator. Mixing comparator - * during the lifecycle of a heap will result in undefined behavior. - */ -function pop(${struct} storage self) internal returns (${valueType}) { - return pop(self, Comparators.lt); -} - -/** - * @dev Remove (and return) the root element for the heap using the provided comparator. - * - * NOTE: All inserting and removal from a heap should always be done using the same comparator. Mixing comparator - * during the lifecycle of a heap will result in undefined behavior. - */ -function pop( - ${struct} storage self, - function(uint256, uint256) view returns (bool) comp -) internal returns (${valueType}) { - unchecked { - ${indexType} size = length(self); - if (size == 0) Panic.panic(Panic.EMPTY_ARRAY_POP); - - ${indexType} last = size - 1; - - // get root location (in the data array) and value - ${node} storage rootNode = _unsafeNodeAccess(self, 0); - ${indexType} rootIdx = rootNode.index; - ${node} storage rootData = _unsafeNodeAccess(self, rootIdx); - ${node} storage lastNode = _unsafeNodeAccess(self, last); - ${valueType} rootDataValue = rootData.value; - - // if root is not the last element of the data array (that will get popped), reorder the data array. - if (rootIdx != last) { - // get details about the value stored in the last element of the array (that will get popped) - ${indexType} lastDataIdx = lastNode.lookup; - ${valueType} lastDataValue = lastNode.value; - // copy these values to the location of the root (that is safe, and that we no longer use) - rootData.value = lastDataValue; - rootData.lookup = lastDataIdx; - // update the tree node that used to point to that last element (value now located where the root was) - _unsafeNodeAccess(self, lastDataIdx).index = rootIdx; - } - - // get last leaf location (in the data array) and value - ${indexType} lastIdx = lastNode.index; - ${valueType} lastValue = _unsafeNodeAccess(self, lastIdx).value; - - // move the last leaf to the root, pop last leaf ... - rootNode.index = lastIdx; - _unsafeNodeAccess(self, lastIdx).lookup = 0; - self.data.pop(); - - // ... and heapify - _siftDown(self, last, 0, lastValue, comp); - - // return root value - return rootDataValue; - } -} - -/** - * @dev Insert a new element in the heap using the default comparator. - * - * NOTE: All inserting and removal from a heap should always be done using the same comparator. Mixing comparator - * during the lifecycle of a heap will result in undefined behavior. - */ -function insert(${struct} storage self, ${valueType} value) internal { - insert(self, value, Comparators.lt); -} - -/** - * @dev Insert a new element in the heap using the provided comparator. - * - * NOTE: All inserting and removal from a heap should always be done using the same comparator. Mixing comparator - * during the lifecycle of a heap will result in undefined behavior. - */ -function insert( - ${struct} storage self, - ${valueType} value, - function(uint256, uint256) view returns (bool) comp -) internal { - ${indexType} size = length(self); - if (size == type(${indexType}).max) Panic.panic(Panic.RESOURCE_ERROR); - - self.data.push(${struct}Node({index: size, lookup: size, value: value})); - _siftUp(self, size, value, comp); -} - -/** - * @dev Return the root element for the heap, and replace it with a new value, using the default comparator. - * This is equivalent to using {pop} and {insert}, but requires only one rebalancing operation. - * - * NOTE: All inserting and removal from a heap should always be done using the same comparator. Mixing comparator - * during the lifecycle of a heap will result in undefined behavior. - */ -function replace(${struct} storage self, ${valueType} newValue) internal returns (${valueType}) { - return replace(self, newValue, Comparators.lt); -} - -/** - * @dev Return the root element for the heap, and replace it with a new value, using the provided comparator. - * This is equivalent to using {pop} and {insert}, but requires only one rebalancing operation. - * - * NOTE: All inserting and removal from a heap should always be done using the same comparator. Mixing comparator - * during the lifecycle of a heap will result in undefined behavior. - */ -function replace( - ${struct} storage self, - ${valueType} newValue, - function(uint256, uint256) view returns (bool) comp -) internal returns (${valueType}) { - ${indexType} size = length(self); - if (size == 0) Panic.panic(Panic.EMPTY_ARRAY_POP); - - // position of the node that holds the data for the root - ${indexType} rootIdx = _unsafeNodeAccess(self, 0).index; - // storage pointer to the node that holds the data for the root - ${node} storage rootData = _unsafeNodeAccess(self, rootIdx); - - // cache old value and replace it - ${valueType} oldValue = rootData.value; - rootData.value = newValue; - - // re-heapify - _siftDown(self, size, 0, newValue, comp); - - // return old root value - return oldValue; -} - -/** - * @dev Returns the number of elements in the heap. - */ -function length(${struct} storage self) internal view returns (${indexType}) { - return self.data.length.to${capitalize(indexType)}(); -} - -/** - * @dev Removes all elements in the heap. - */ -function clear(${struct} storage self) internal { - ${struct}Node[] storage data = self.data; - assembly ("memory-safe") { - sstore(data.slot, 0) - } -} - -/** - * @dev Swap node \`i\` and \`j\` in the tree. - */ -function _swap(${struct} storage self, ${indexType} i, ${indexType} j) private { - ${node} storage ni = _unsafeNodeAccess(self, i); - ${node} storage nj = _unsafeNodeAccess(self, j); - ${indexType} ii = ni.index; - ${indexType} jj = nj.index; - // update pointers to the data (swap the value) - ni.index = jj; - nj.index = ii; - // update lookup pointers for consistency - _unsafeNodeAccess(self, ii).lookup = j; - _unsafeNodeAccess(self, jj).lookup = i; -} - -/** - * @dev Perform heap maintenance on \`self\`, starting at position \`pos\` (with the \`value\`), using \`comp\` as a - * comparator, and moving toward the leaves of the underlying tree. - * - * NOTE: This is a private function that is called in a trusted context with already cached parameters. \`length\` - * and \`value\` could be extracted from \`self\` and \`pos\`, but that would require redundant storage read. These - * parameters are not verified. It is the caller role to make sure the parameters are correct. - */ -function _siftDown( - ${struct} storage self, - ${indexType} size, - ${indexType} pos, - ${valueType} value, - function(uint256, uint256) view returns (bool) comp -) private { - uint256 left = 2 * pos + 1; // this could overflow ${indexType} - uint256 right = 2 * pos + 2; // this could overflow ${indexType} - - if (right < size) { - // the check guarantees that \`left\` and \`right\` are both valid ${indexType} - ${indexType} lIndex = ${indexType}(left); - ${indexType} rIndex = ${indexType}(right); - ${valueType} lValue = _unsafeNodeAccess(self, _unsafeNodeAccess(self, lIndex).index).value; - ${valueType} rValue = _unsafeNodeAccess(self, _unsafeNodeAccess(self, rIndex).index).value; - if (comp(lValue, value) || comp(rValue, value)) { - ${indexType} index = ${indexType}(comp(lValue, rValue).ternary(lIndex, rIndex)); - _swap(self, pos, index); - _siftDown(self, size, index, value, comp); - } - } else if (left < size) { - // the check guarantees that \`left\` is a valid ${indexType} - ${indexType} lIndex = ${indexType}(left); - ${valueType} lValue = _unsafeNodeAccess(self, _unsafeNodeAccess(self, lIndex).index).value; - if (comp(lValue, value)) { - _swap(self, pos, lIndex); - _siftDown(self, size, lIndex, value, comp); - } - } -} - -/** - * @dev Perform heap maintenance on \`self\`, starting at position \`pos\` (with the \`value\`), using \`comp\` as a - * comparator, and moving toward the root of the underlying tree. - * - * NOTE: This is a private function that is called in a trusted context with already cached parameters. \`value\` - * could be extracted from \`self\` and \`pos\`, but that would require redundant storage read. These parameters are not - * verified. It is the caller role to make sure the parameters are correct. - */ -function _siftUp( - ${struct} storage self, - ${indexType} pos, - ${valueType} value, - function(uint256, uint256) view returns (bool) comp -) private { - unchecked { - while (pos > 0) { - ${indexType} parent = (pos - 1) / 2; - ${valueType} parentValue = _unsafeNodeAccess(self, _unsafeNodeAccess(self, parent).index).value; - if (comp(parentValue, value)) break; - _swap(self, pos, parent); - pos = parent; - } - } -} - -function _unsafeNodeAccess( - ${struct} storage self, - ${indexType} pos -) private pure returns (${node} storage result) { - assembly ("memory-safe") { - mstore(0x00, self.slot) - result.slot := add(keccak256(0x00, 0x20), ${blockSize == 1 ? 'pos' : `mul(pos, ${blockSize})`}) - } -} -`; - -// GENERATE -module.exports = format( - header.trimEnd(), - 'library Heap {', - format( - [].concat( - 'using Math for *;', - 'using SafeCast for *;', - '', - TYPES.map(type => generate(type)), - ), - ).trimEnd(), - '}', -); diff --git a/scripts/generate/templates/Heap.opts.js b/scripts/generate/templates/Heap.opts.js deleted file mode 100644 index 8b8be0afdfa..00000000000 --- a/scripts/generate/templates/Heap.opts.js +++ /dev/null @@ -1,13 +0,0 @@ -const makeType = (valueSize, indexSize) => ({ - struct: `Uint${valueSize}Heap`, - node: `Uint${valueSize}HeapNode`, - valueSize, - valueType: `uint${valueSize}`, - indexSize, - indexType: `uint${indexSize}`, - blockSize: Math.ceil((valueSize + 2 * indexSize) / 256), -}); - -module.exports = { - TYPES: [makeType(256, 64), makeType(208, 24)], -}; diff --git a/scripts/generate/templates/Heap.t.js b/scripts/generate/templates/Heap.t.js deleted file mode 100644 index 04b3152ba3a..00000000000 --- a/scripts/generate/templates/Heap.t.js +++ /dev/null @@ -1,89 +0,0 @@ -const format = require('../format-lines'); -const { TYPES } = require('./Heap.opts'); - -/* eslint-disable max-len */ -const header = `\ -pragma solidity ^0.8.20; - -import {Test} from "forge-std/Test.sol"; -import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; -import {Heap} from "@openzeppelin/contracts/utils/structs/Heap.sol"; -import {Comparators} from "@openzeppelin/contracts/utils/Comparators.sol"; -`; - -const generate = ({ struct, valueType }) => `\ -contract ${struct}Test is Test { - using Heap for Heap.${struct}; - - Heap.${struct} internal heap; - - function _validateHeap(function(uint256, uint256) view returns (bool) comp) internal { - for (uint32 i = 0; i < heap.length(); ++i) { - // lookups - assertEq(i, heap.data[heap.data[i].index].lookup); - assertEq(i, heap.data[heap.data[i].lookup].index); - - // ordering: each node has a value bigger then its parent - if (i > 0) - assertFalse(comp(heap.data[heap.data[i].index].value, heap.data[heap.data[(i - 1) / 2].index].value)); - } - } - - function testFuzz(${valueType}[] calldata input) public { - vm.assume(input.length < 0x20); - assertEq(heap.length(), 0); - - uint256 min = type(uint256).max; - for (uint256 i = 0; i < input.length; ++i) { - heap.insert(input[i]); - assertEq(heap.length(), i + 1); - _validateHeap(Comparators.lt); - - min = Math.min(min, input[i]); - assertEq(heap.peek(), min); - } - - uint256 max = 0; - for (uint256 i = 0; i < input.length; ++i) { - ${valueType} top = heap.peek(); - ${valueType} pop = heap.pop(); - assertEq(heap.length(), input.length - i - 1); - _validateHeap(Comparators.lt); - - assertEq(pop, top); - assertGe(pop, max); - max = pop; - } - } - - function testFuzzGt(${valueType}[] calldata input) public { - vm.assume(input.length < 0x20); - assertEq(heap.length(), 0); - - uint256 max = 0; - for (uint256 i = 0; i < input.length; ++i) { - heap.insert(input[i], Comparators.gt); - assertEq(heap.length(), i + 1); - _validateHeap(Comparators.gt); - - max = Math.max(max, input[i]); - assertEq(heap.peek(), max); - } - - uint256 min = type(uint256).max; - for (uint256 i = 0; i < input.length; ++i) { - ${valueType} top = heap.peek(); - ${valueType} pop = heap.pop(Comparators.gt); - assertEq(heap.length(), input.length - i - 1); - _validateHeap(Comparators.gt); - - assertEq(pop, top); - assertLe(pop, min); - min = pop; - } - } -} -`; - -// GENERATE -module.exports = format(header, ...TYPES.map(type => generate(type))); diff --git a/test/utils/structs/Heap.t.sol b/test/utils/structs/Heap.t.sol index b9d0b98787c..434f37f669a 100644 --- a/test/utils/structs/Heap.t.sol +++ b/test/utils/structs/Heap.t.sol @@ -1,5 +1,4 @@ // SPDX-License-Identifier: MIT -// This file was procedurally generated from scripts/generate/templates/Heap.t.js. pragma solidity ^0.8.20; @@ -14,14 +13,8 @@ contract Uint256HeapTest is Test { Heap.Uint256Heap internal heap; function _validateHeap(function(uint256, uint256) view returns (bool) comp) internal { - for (uint32 i = 0; i < heap.length(); ++i) { - // lookups - assertEq(i, heap.data[heap.data[i].index].lookup); - assertEq(i, heap.data[heap.data[i].lookup].index); - - // ordering: each node has a value bigger then its parent - if (i > 0) - assertFalse(comp(heap.data[heap.data[i].index].value, heap.data[heap.data[(i - 1) / 2].index].value)); + for (uint32 i = 1; i < heap.length(); ++i) { + assertFalse(comp(heap.tree[i], heap.tree[(i - 1) / 2])); } } @@ -79,75 +72,3 @@ contract Uint256HeapTest is Test { } } } - -contract Uint208HeapTest is Test { - using Heap for Heap.Uint208Heap; - - Heap.Uint208Heap internal heap; - - function _validateHeap(function(uint256, uint256) view returns (bool) comp) internal { - for (uint32 i = 0; i < heap.length(); ++i) { - // lookups - assertEq(i, heap.data[heap.data[i].index].lookup); - assertEq(i, heap.data[heap.data[i].lookup].index); - - // ordering: each node has a value bigger then its parent - if (i > 0) - assertFalse(comp(heap.data[heap.data[i].index].value, heap.data[heap.data[(i - 1) / 2].index].value)); - } - } - - function testFuzz(uint208[] calldata input) public { - vm.assume(input.length < 0x20); - assertEq(heap.length(), 0); - - uint256 min = type(uint256).max; - for (uint256 i = 0; i < input.length; ++i) { - heap.insert(input[i]); - assertEq(heap.length(), i + 1); - _validateHeap(Comparators.lt); - - min = Math.min(min, input[i]); - assertEq(heap.peek(), min); - } - - uint256 max = 0; - for (uint256 i = 0; i < input.length; ++i) { - uint208 top = heap.peek(); - uint208 pop = heap.pop(); - assertEq(heap.length(), input.length - i - 1); - _validateHeap(Comparators.lt); - - assertEq(pop, top); - assertGe(pop, max); - max = pop; - } - } - - function testFuzzGt(uint208[] calldata input) public { - vm.assume(input.length < 0x20); - assertEq(heap.length(), 0); - - uint256 max = 0; - for (uint256 i = 0; i < input.length; ++i) { - heap.insert(input[i], Comparators.gt); - assertEq(heap.length(), i + 1); - _validateHeap(Comparators.gt); - - max = Math.max(max, input[i]); - assertEq(heap.peek(), max); - } - - uint256 min = type(uint256).max; - for (uint256 i = 0; i < input.length; ++i) { - uint208 top = heap.peek(); - uint208 pop = heap.pop(Comparators.gt); - assertEq(heap.length(), input.length - i - 1); - _validateHeap(Comparators.gt); - - assertEq(pop, top); - assertLe(pop, min); - min = pop; - } - } -} diff --git a/test/utils/structs/Heap.test.js b/test/utils/structs/Heap.test.js index 7e95a0e7adb..6d751205c7a 100644 --- a/test/utils/structs/Heap.test.js +++ b/test/utils/structs/Heap.test.js @@ -3,8 +3,6 @@ const { expect } = require('chai'); const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); const { PANIC_CODES } = require('@nomicfoundation/hardhat-chai-matchers/panic'); -const { TYPES } = require('../../../scripts/generate/templates/Heap.opts'); - async function fixture() { const mock = await ethers.deployContract('$Heap'); return { mock }; @@ -15,117 +13,101 @@ describe('Heap', function () { Object.assign(this, await loadFixture(fixture)); }); - for (const { struct, valueType } of TYPES) { - describe(struct, function () { - const popEvent = `return$pop_Heap_${struct}`; - const replaceEvent = `return$replace_Heap_${struct}_${valueType}`; - - beforeEach(async function () { - this.helper = { - clear: (...args) => this.mock[`$clear_Heap_${struct}`](0, ...args), - insert: (...args) => this.mock[`$insert(uint256,${valueType})`](0, ...args), - replace: (...args) => this.mock[`$replace(uint256,${valueType})`](0, ...args), - length: (...args) => this.mock[`$length_Heap_${struct}`](0, ...args), - pop: (...args) => this.mock[`$pop_Heap_${struct}`](0, ...args), - peek: (...args) => this.mock[`$peek_Heap_${struct}`](0, ...args), - }; - }); - - it('starts empty', async function () { - expect(await this.helper.length()).to.equal(0n); - }); - - it('peek, pop and replace from empty', async function () { - await expect(this.helper.peek()).to.be.revertedWithPanic(PANIC_CODES.ARRAY_ACCESS_OUT_OF_BOUNDS); - await expect(this.helper.pop()).to.be.revertedWithPanic(PANIC_CODES.POP_ON_EMPTY_ARRAY); - await expect(this.helper.replace(0n)).to.be.revertedWithPanic(PANIC_CODES.POP_ON_EMPTY_ARRAY); - }); - - it('clear', async function () { - await this.helper.insert(42n); - - expect(await this.helper.length()).to.equal(1n); - expect(await this.helper.peek()).to.equal(42n); + describe('Uint256Heap', function () { + it('starts empty', async function () { + expect(await this.mock.$length(0)).to.equal(0n); + }); - await this.helper.clear(); + it('peek, pop and replace from empty', async function () { + await expect(this.mock.$peek(0)).to.be.revertedWithPanic(PANIC_CODES.ARRAY_ACCESS_OUT_OF_BOUNDS); + await expect(this.mock.$pop(0)).to.be.revertedWithPanic(PANIC_CODES.POP_ON_EMPTY_ARRAY); + await expect(this.mock.$replace(0, 0n)).to.be.revertedWithPanic(PANIC_CODES.POP_ON_EMPTY_ARRAY); + }); - expect(await this.helper.length()).to.equal(0n); - await expect(this.helper.peek()).to.be.revertedWithPanic(PANIC_CODES.ARRAY_ACCESS_OUT_OF_BOUNDS); - }); + it('clear', async function () { + await this.mock.$insert(0, 42n); - it('support duplicated items', async function () { - expect(await this.helper.length()).to.equal(0n); + expect(await this.mock.$length(0)).to.equal(1n); + expect(await this.mock.$peek(0)).to.equal(42n); - // insert 5 times - await this.helper.insert(42n); - await this.helper.insert(42n); - await this.helper.insert(42n); - await this.helper.insert(42n); - await this.helper.insert(42n); + await this.mock.$clear(0); - // pop 5 times - await expect(this.helper.pop()).to.emit(this.mock, popEvent).withArgs(42n); - await expect(this.helper.pop()).to.emit(this.mock, popEvent).withArgs(42n); - await expect(this.helper.pop()).to.emit(this.mock, popEvent).withArgs(42n); - await expect(this.helper.pop()).to.emit(this.mock, popEvent).withArgs(42n); - await expect(this.helper.pop()).to.emit(this.mock, popEvent).withArgs(42n); + expect(await this.mock.$length(0)).to.equal(0n); + await expect(this.mock.$peek(0)).to.be.revertedWithPanic(PANIC_CODES.ARRAY_ACCESS_OUT_OF_BOUNDS); + }); - // popping a 6th time panics - await expect(this.helper.pop()).to.be.revertedWithPanic(PANIC_CODES.POP_ON_EMPTY_ARRAY); - }); + it('support duplicated items', async function () { + expect(await this.mock.$length(0)).to.equal(0n); + + // insert 5 times + await this.mock.$insert(0, 42n); + await this.mock.$insert(0, 42n); + await this.mock.$insert(0, 42n); + await this.mock.$insert(0, 42n); + await this.mock.$insert(0, 42n); + + // pop 5 times + await expect(this.mock.$pop(0)).to.emit(this.mock, 'return$pop').withArgs(42n); + await expect(this.mock.$pop(0)).to.emit(this.mock, 'return$pop').withArgs(42n); + await expect(this.mock.$pop(0)).to.emit(this.mock, 'return$pop').withArgs(42n); + await expect(this.mock.$pop(0)).to.emit(this.mock, 'return$pop').withArgs(42n); + await expect(this.mock.$pop(0)).to.emit(this.mock, 'return$pop').withArgs(42n); + + // popping a 6th time panics + await expect(this.mock.$pop(0)).to.be.revertedWithPanic(PANIC_CODES.POP_ON_EMPTY_ARRAY); + }); - it('insert, pop and replace', async function () { - const heap = []; - for (const { op, value } of [ - { op: 'insert', value: 712 }, // [712] - { op: 'insert', value: 20 }, // [20, 712] - { op: 'insert', value: 4337 }, // [20, 712, 4437] - { op: 'pop' }, // 20, [712, 4437] - { op: 'insert', value: 1559 }, // [712, 1559, 4437] - { op: 'insert', value: 165 }, // [165, 712, 1559, 4437] - { op: 'insert', value: 155 }, // [155, 165, 712, 1559, 4437] - { op: 'insert', value: 7702 }, // [155, 165, 712, 1559, 4437, 7702] - { op: 'pop' }, // 155, [165, 712, 1559, 4437, 7702] - { op: 'replace', value: 721 }, // 165, [712, 721, 1559, 4437, 7702] - { op: 'pop' }, // 712, [721, 1559, 4437, 7702] - { op: 'pop' }, // 721, [1559, 4437, 7702] - { op: 'pop' }, // 1559, [4437, 7702] - { op: 'pop' }, // 4437, [7702] - { op: 'pop' }, // 7702, [] - { op: 'pop' }, // panic - { op: 'replace', value: '1363' }, // panic - ]) { - switch (op) { - case 'insert': - await this.helper.insert(value); + it('insert, pop and replace', async function () { + const heap = []; + for (const { op, value } of [ + { op: 'insert', value: 712 }, // [712] + { op: 'insert', value: 20 }, // [20, 712] + { op: 'insert', value: 4337 }, // [20, 712, 4437] + { op: 'pop' }, // 20, [712, 4437] + { op: 'insert', value: 1559 }, // [712, 1559, 4437] + { op: 'insert', value: 165 }, // [165, 712, 1559, 4437] + { op: 'insert', value: 155 }, // [155, 165, 712, 1559, 4437] + { op: 'insert', value: 7702 }, // [155, 165, 712, 1559, 4437, 7702] + { op: 'pop' }, // 155, [165, 712, 1559, 4437, 7702] + { op: 'replace', value: 721 }, // 165, [712, 721, 1559, 4437, 7702] + { op: 'pop' }, // 712, [721, 1559, 4437, 7702] + { op: 'pop' }, // 721, [1559, 4437, 7702] + { op: 'pop' }, // 1559, [4437, 7702] + { op: 'pop' }, // 4437, [7702] + { op: 'pop' }, // 7702, [] + { op: 'pop' }, // panic + { op: 'replace', value: '1363' }, // panic + ]) { + switch (op) { + case 'insert': + await this.mock.$insert(0, value); + heap.push(value); + heap.sort((a, b) => a - b); + break; + case 'pop': + if (heap.length == 0) { + await expect(this.mock.$pop(0)).to.be.revertedWithPanic(PANIC_CODES.POP_ON_EMPTY_ARRAY); + } else { + await expect(this.mock.$pop(0)).to.emit(this.mock, 'return$pop').withArgs(heap.shift()); + } + break; + case 'replace': + if (heap.length == 0) { + await expect(this.mock.$replace(0, value)).to.be.revertedWithPanic(PANIC_CODES.POP_ON_EMPTY_ARRAY); + } else { + await expect(this.mock.$replace(0, value)).to.emit(this.mock, 'return$replace').withArgs(heap.shift()); heap.push(value); heap.sort((a, b) => a - b); - break; - case 'pop': - if (heap.length == 0) { - await expect(this.helper.pop()).to.be.revertedWithPanic(PANIC_CODES.POP_ON_EMPTY_ARRAY); - } else { - await expect(this.helper.pop()).to.emit(this.mock, popEvent).withArgs(heap.shift()); - } - break; - case 'replace': - if (heap.length == 0) { - await expect(this.helper.replace(value)).to.be.revertedWithPanic(PANIC_CODES.POP_ON_EMPTY_ARRAY); - } else { - await expect(this.helper.replace(value)).to.emit(this.mock, replaceEvent).withArgs(heap.shift()); - heap.push(value); - heap.sort((a, b) => a - b); - } - break; - } - expect(await this.helper.length()).to.equal(heap.length); - if (heap.length == 0) { - await expect(this.helper.peek()).to.be.revertedWithPanic(PANIC_CODES.ARRAY_ACCESS_OUT_OF_BOUNDS); - } else { - expect(await this.helper.peek()).to.equal(heap[0]); - } + } + break; + } + expect(await this.mock.$length(0)).to.equal(heap.length); + if (heap.length == 0) { + await expect(this.mock.$peek(0)).to.be.revertedWithPanic(PANIC_CODES.ARRAY_ACCESS_OUT_OF_BOUNDS); + } else { + expect(await this.mock.$peek(0)).to.equal(heap[0]); } - }); + } }); - } + }); }); From 204ffee5315f7abe72a3b09f41537bc319a10944 Mon Sep 17 00:00:00 2001 From: cairo Date: Thu, 19 Sep 2024 15:16:19 +0200 Subject: [PATCH 06/84] Clarify reference commits and licenses (#5205) --- contracts/utils/cryptography/P256.sol | 6 +++--- contracts/utils/cryptography/RSA.sol | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/utils/cryptography/P256.sol b/contracts/utils/cryptography/P256.sol index 1b488c180be..9aa7be3c007 100644 --- a/contracts/utils/cryptography/P256.sol +++ b/contracts/utils/cryptography/P256.sol @@ -11,9 +11,9 @@ import {Errors} from "../Errors.sol"; * and cryptographic standards. Some notable examples include Apple's Secure Enclave and Android's Keystore * as well as authentication protocols like FIDO2. * - * Based on the original https://github.com/itsobvioustech/aa-passkeys-wallet/blob/main/src/Secp256r1.sol[implementation of itsobvioustech]. - * Heavily inspired in https://github.com/maxrobot/elliptic-solidity/blob/master/contracts/Secp256r1.sol[maxrobot] and - * https://github.com/tdrerup/elliptic-curve-solidity/blob/master/contracts/curves/EllipticCurve.sol[tdrerup] implementations. + * Based on the original https://github.com/itsobvioustech/aa-passkeys-wallet/blob/d3d423f28a4d8dfcb203c7fa0c47f42592a7378e/src/Secp256r1.sol[implementation of itsobvioustech] (GNU General Public License v3.0). + * Heavily inspired in https://github.com/maxrobot/elliptic-solidity/blob/c4bb1b6e8ae89534d8db3a6b3a6b52219100520f/contracts/Secp256r1.sol[maxrobot] and + * https://github.com/tdrerup/elliptic-curve-solidity/blob/59a9c25957d4d190eff53b6610731d81a077a15e/contracts/curves/EllipticCurve.sol[tdrerup] implementations. */ library P256 { struct JPoint { diff --git a/contracts/utils/cryptography/RSA.sol b/contracts/utils/cryptography/RSA.sol index 1b5860c3b2c..9c2205d8f79 100644 --- a/contracts/utils/cryptography/RSA.sol +++ b/contracts/utils/cryptography/RSA.sol @@ -10,7 +10,7 @@ import {Math} from "../math/Math.sol"; * The padding follows the EMSA-PKCS1-v1_5-ENCODE encoding definition as per section 9.2 of the RFC. This padding makes * RSA semantically secure for signing messages. * - * Inspired by https://github.com/adria0/SolRsaVerify[Adrià Massanet's work] + * Inspired by https://github.com/adria0/SolRsaVerify/blob/79c6182cabb9102ea69d4a2e996816091d5f1cd1[Adrià Massanet's work] (GNU General Public License v3.0). */ library RSA { /** From f3825ab335662879d47a42236895c6cd2485e381 Mon Sep 17 00:00:00 2001 From: cairo Date: Thu, 19 Sep 2024 15:16:52 +0200 Subject: [PATCH 07/84] Clarify mapping named parameters (#5204) --- contracts/governance/extensions/GovernorCountingFractional.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/governance/extensions/GovernorCountingFractional.sol b/contracts/governance/extensions/GovernorCountingFractional.sol index 5a553fbdb44..06e7897dc48 100644 --- a/contracts/governance/extensions/GovernorCountingFractional.sol +++ b/contracts/governance/extensions/GovernorCountingFractional.sol @@ -43,7 +43,7 @@ abstract contract GovernorCountingFractional is Governor { /** * @dev Mapping from proposal ID to vote tallies for that proposal. */ - mapping(uint256 => ProposalVote) private _proposalVotes; + mapping(uint256 proposalId => ProposalVote) private _proposalVotes; /** * @dev A fractional vote params uses more votes than are available for that user. From 8a309ab5ecab69455812e01829c6425059cc5e99 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 19 Sep 2024 15:54:54 +0200 Subject: [PATCH 08/84] Update documentation of helper interface (#5179) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernesto García --- .../proxy/transparent/TransparentUpgradeableProxy.sol | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/contracts/proxy/transparent/TransparentUpgradeableProxy.sol b/contracts/proxy/transparent/TransparentUpgradeableProxy.sol index 6344eb9fa5f..5304f4311bc 100644 --- a/contracts/proxy/transparent/TransparentUpgradeableProxy.sol +++ b/contracts/proxy/transparent/TransparentUpgradeableProxy.sol @@ -15,12 +15,7 @@ import {ProxyAdmin} from "./ProxyAdmin.sol"; * include them in the ABI so this interface must be used to interact with it. */ interface ITransparentUpgradeableProxy is IERC1967 { - /** - * @dev Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call - * encoded in `data`. - * - * See {UUPSUpgradeable-upgradeToAndCall} - */ + /// @dev See {UUPSUpgradeable-upgradeToAndCall} function upgradeToAndCall(address newImplementation, bytes calldata data) external payable; } From b1f6bbe69f70d106f4de55845d8abe51ef9b91dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Thu, 19 Sep 2024 09:08:52 -0600 Subject: [PATCH 09/84] Add note about memory manipulation in MerkleTree (#5213) --- contracts/utils/structs/MerkleTree.sol | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/contracts/utils/structs/MerkleTree.sol b/contracts/utils/structs/MerkleTree.sol index c4933f79ef8..c3768ed8c74 100644 --- a/contracts/utils/structs/MerkleTree.sol +++ b/contracts/utils/structs/MerkleTree.sol @@ -70,7 +70,10 @@ library MerkleTree { * should be pushed to it using the custom push function, which should be the same one as used during the setup. * * IMPORTANT: Providing a custom hashing function is a security-sensitive operation since it may - * compromise the soundness of the tree. Consider using functions from {Hashes}. + * compromise the soundness of the tree. + * + * NOTE: Consider verifying that the hashing function does not manipulate the memory state directly and that it + * follows the Solidity memory safety rules. Otherwise, it may lead to unexpected behavior. */ function setup( Bytes32PushTree storage self, From 530179a71f435e85ae9df9b9f12b5637cf229e5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Thu, 19 Sep 2024 11:20:04 -0600 Subject: [PATCH 10/84] Disallow empty CircularBuffer setup (#5214) --- contracts/utils/structs/CircularBuffer.sol | 6 ++++++ test/utils/structs/CircularBuffer.test.js | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/contracts/utils/structs/CircularBuffer.sol b/contracts/utils/structs/CircularBuffer.sol index ae4e082595a..1d0adf8196b 100644 --- a/contracts/utils/structs/CircularBuffer.sol +++ b/contracts/utils/structs/CircularBuffer.sol @@ -36,6 +36,11 @@ import {Panic} from "../Panic.sol"; * ``` */ library CircularBuffer { + /** + * @dev Error emitted when trying to setup a buffer with a size of 0. + */ + error InvalidBufferSize(); + /** * @dev Counts the number of items that have been pushed to the buffer. The residuo modulo _data.length indicates * where the next value should be stored. @@ -61,6 +66,7 @@ library CircularBuffer { * Consider a large buffer size may render the function unusable. */ function setup(Bytes32CircularBuffer storage self, uint256 size) internal { + if (size == 0) revert InvalidBufferSize(); clear(self); Arrays.unsafeSetLength(self._data, size); } diff --git a/test/utils/structs/CircularBuffer.test.js b/test/utils/structs/CircularBuffer.test.js index c3d61aaa823..e79ba6923b4 100644 --- a/test/utils/structs/CircularBuffer.test.js +++ b/test/utils/structs/CircularBuffer.test.js @@ -18,6 +18,10 @@ describe('CircularBuffer', function () { Object.assign(this, await loadFixture(fixture)); }); + it('reverts on invalid setup', async function () { + await expect(this.mock.$setup(0, 0)).to.be.revertedWithCustomError(this.mock, 'InvalidBufferSize'); + }); + it('starts empty', async function () { expect(await this.mock.$count(0)).to.equal(0n); expect(await this.mock.$length(0)).to.equal(LENGTH); From e866815c7d14bc34598bf9f6110c0f0398749d2f Mon Sep 17 00:00:00 2001 From: skyge <1506186404li@gmail.com> Date: Mon, 23 Sep 2024 23:16:54 +0800 Subject: [PATCH 11/84] Fix typo in ERC4626 docs (#5222) Co-authored-by: Hadrien Croubois --- docs/modules/ROOT/pages/erc4626.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/modules/ROOT/pages/erc4626.adoc b/docs/modules/ROOT/pages/erc4626.adoc index 977d8160632..79388c0a2e7 100644 --- a/docs/modules/ROOT/pages/erc4626.adoc +++ b/docs/modules/ROOT/pages/erc4626.adoc @@ -29,7 +29,7 @@ image::erc4626-rate-loglogext.png[More exchange rates in logarithmic scale] === The attack -When depositing tokens, the number of shares a user gets is rounded towards zero. This rounding takes away value from the user in favor or the vault (i.e. in favor of all the current share holders). This rounding is often negligible because of the amount at stake. If you deposit 1e9 shares worth of tokens, the rounding will have you lose at most 0.0000001% of your deposit. However if you deposit 10 shares worth of tokens, you could lose 10% of your deposit. Even worse, if you deposit <1 share worth of tokens, then you get 0 shares, and you basically made a donation. +When depositing tokens, the number of shares a user gets is rounded towards zero. This rounding takes away value from the user in favor of the vault (i.e. in favor of all the current share holders). This rounding is often negligible because of the amount at stake. If you deposit 1e9 shares worth of tokens, the rounding will have you lose at most 0.0000001% of your deposit. However if you deposit 10 shares worth of tokens, you could lose 10% of your deposit. Even worse, if you deposit <1 share worth of tokens, then you get 0 shares, and you basically made a donation. For a given amount of assets, the more shares you receive the safer you are. If you want to limit your losses to at most 1%, you need to receive at least 100 shares. From 2f0bc58946db746c0d17a2b9d9a8e13f5a8edd7f Mon Sep 17 00:00:00 2001 From: cairo Date: Mon, 23 Sep 2024 17:17:10 +0200 Subject: [PATCH 12/84] Update and clarify documentation comments (#5206) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Hadrien Croubois Co-authored-by: Ernesto García --- contracts/finance/VestingWalletCliff.sol | 3 +- .../extensions/GovernorCountingFractional.sol | 2 +- contracts/interfaces/IERC1363Spender.sol | 2 +- contracts/utils/cryptography/P256.sol | 10 +- contracts/utils/cryptography/RSA.sol | 23 ++-- contracts/utils/structs/CircularBuffer.sol | 5 +- docs/modules/ROOT/pages/utilities.adoc | 2 +- test/utils/cryptography/RSA.test.js | 101 +++++++++--------- 8 files changed, 77 insertions(+), 71 deletions(-) diff --git a/contracts/finance/VestingWalletCliff.sol b/contracts/finance/VestingWalletCliff.sol index d98be640bc7..034dd9cf6ea 100644 --- a/contracts/finance/VestingWalletCliff.sol +++ b/contracts/finance/VestingWalletCliff.sol @@ -17,8 +17,7 @@ abstract contract VestingWalletCliff is VestingWallet { error InvalidCliffDuration(uint64 cliffSeconds, uint64 durationSeconds); /** - * @dev Sets the sender as the initial owner, the beneficiary as the pending owner, the start timestamp, the - * vesting duration and the duration of the cliff of the vesting wallet. + * @dev Set the start timestamp of the vesting wallet cliff. */ constructor(uint64 cliffSeconds) { if (cliffSeconds > duration()) { diff --git a/contracts/governance/extensions/GovernorCountingFractional.sol b/contracts/governance/extensions/GovernorCountingFractional.sol index 06e7897dc48..3a4fb664a53 100644 --- a/contracts/governance/extensions/GovernorCountingFractional.sol +++ b/contracts/governance/extensions/GovernorCountingFractional.sol @@ -145,7 +145,7 @@ abstract contract GovernorCountingFractional is Governor { uint256 againstVotes = 0; uint256 forVotes = 0; uint256 abstainVotes = 0; - uint256 usedWeight; + uint256 usedWeight = 0; // For clarity of event indexing, fractional voting must be clearly advertised in the "support" field. // diff --git a/contracts/interfaces/IERC1363Spender.sol b/contracts/interfaces/IERC1363Spender.sol index b4bf3f42c3d..4e9aba7d7f7 100644 --- a/contracts/interfaces/IERC1363Spender.sol +++ b/contracts/interfaces/IERC1363Spender.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.20; /** - * @title ERC1363Spender + * @title IERC1363Spender * @dev Interface for any contract that wants to support `approveAndCall` * from ERC-1363 token contracts. */ diff --git a/contracts/utils/cryptography/P256.sol b/contracts/utils/cryptography/P256.sol index 9aa7be3c007..60c5fec3d8b 100644 --- a/contracts/utils/cryptography/P256.sol +++ b/contracts/utils/cryptography/P256.sol @@ -142,7 +142,7 @@ library P256 { /** * @dev Checks if (x, y) are valid coordinates of a point on the curve. - * In particular this function checks that x <= P and y <= P. + * In particular this function checks that x < P and y < P. */ function isValidPublicKey(bytes32 x, bytes32 y) internal pure returns (bool result) { assembly ("memory-safe") { @@ -239,7 +239,7 @@ library P256 { } /** - * @dev Compute P·u1 + Q·u2 using the precomputed points for P and Q (see {_preComputeJacobianPoints}). + * @dev Compute G·u1 + P·u2 using the precomputed points for G and P (see {_preComputeJacobianPoints}). * * Uses Strauss Shamir trick for EC multiplication * https://stackoverflow.com/questions/50993471/ec-scalar-multiplication-with-strauss-shamir-method @@ -292,17 +292,17 @@ library P256 { points[0x04] = JPoint(GX, GY, 1); // 0,1 (g) points[0x02] = _jDoublePoint(points[0x01]); // 2,0 (2p) points[0x08] = _jDoublePoint(points[0x04]); // 0,2 (2g) - points[0x03] = _jAddPoint(points[0x01], points[0x02]); // 3,0 (3p) + points[0x03] = _jAddPoint(points[0x01], points[0x02]); // 3,0 (p+2p = 3p) points[0x05] = _jAddPoint(points[0x01], points[0x04]); // 1,1 (p+g) points[0x06] = _jAddPoint(points[0x02], points[0x04]); // 2,1 (2p+g) points[0x07] = _jAddPoint(points[0x03], points[0x04]); // 3,1 (3p+g) points[0x09] = _jAddPoint(points[0x01], points[0x08]); // 1,2 (p+2g) points[0x0a] = _jAddPoint(points[0x02], points[0x08]); // 2,2 (2p+2g) points[0x0b] = _jAddPoint(points[0x03], points[0x08]); // 3,2 (3p+2g) - points[0x0c] = _jAddPoint(points[0x04], points[0x08]); // 0,3 (g+2g) + points[0x0c] = _jAddPoint(points[0x04], points[0x08]); // 0,3 (g+2g = 3g) points[0x0d] = _jAddPoint(points[0x01], points[0x0c]); // 1,3 (p+3g) points[0x0e] = _jAddPoint(points[0x02], points[0x0c]); // 2,3 (2p+3g) - points[0x0f] = _jAddPoint(points[0x03], points[0x0C]); // 3,3 (3p+3g) + points[0x0f] = _jAddPoint(points[0x03], points[0x0c]); // 3,3 (3p+3g) } function _jAddPoint(JPoint memory p1, JPoint memory p2) private pure returns (JPoint memory) { diff --git a/contracts/utils/cryptography/RSA.sol b/contracts/utils/cryptography/RSA.sol index 9c2205d8f79..6d355c60e2f 100644 --- a/contracts/utils/cryptography/RSA.sol +++ b/contracts/utils/cryptography/RSA.sol @@ -14,7 +14,7 @@ import {Math} from "../math/Math.sol"; */ library RSA { /** - * @dev Same as {pkcs1} but using SHA256 to calculate the digest of `data`. + * @dev Same as {pkcs1Sha256} but using SHA256 to calculate the digest of `data`. */ function pkcs1Sha256( bytes memory data, @@ -22,15 +22,16 @@ library RSA { bytes memory e, bytes memory n ) internal view returns (bool) { - return pkcs1(sha256(data), s, e, n); + return pkcs1Sha256(sha256(data), s, e, n); } /** * @dev Verifies a PKCSv1.5 signature given a digest according to the verification - * method described in https://datatracker.ietf.org/doc/html/rfc8017#section-8.2.2[section 8.2.2 of RFC8017]. + * method described in https://datatracker.ietf.org/doc/html/rfc8017#section-8.2.2[section 8.2.2 of RFC8017] with support + * for explicit or implicit NULL parameters in the DigestInfo (no other optional parameters are supported). * - * IMPORTANT: Although this function allows for it, using n of length 1024 bits is considered unsafe. - * Consider using at least 2048 bits. + * IMPORTANT: For security reason, this function requires the signature and modulus to have a length of at least 2048 bits. + * If you use a smaller key, consider replacing it with a larger, more secure, one. * * WARNING: PKCS#1 v1.5 allows for replayability given the message may contain arbitrary optional parameters in the * DigestInfo. Consider using an onchain nonce or unique identifier to include in the message to prevent replay attacks. @@ -40,12 +41,12 @@ library RSA { * @param e is the exponent of the public key * @param n is the modulus of the public key */ - function pkcs1(bytes32 digest, bytes memory s, bytes memory e, bytes memory n) internal view returns (bool) { + function pkcs1Sha256(bytes32 digest, bytes memory s, bytes memory e, bytes memory n) internal view returns (bool) { unchecked { // cache and check length uint256 length = n.length; if ( - length < 0x40 || // PKCS#1 padding is slightly less than 0x40 bytes at the bare minimum + length < 0x100 || // Enforce 2048 bits minimum length != s.length // signature must have the same length as the finite field ) { return false; @@ -94,13 +95,13 @@ library RSA { // it should be at 32 (digest) + 2 bytes from the end. To those 34 bytes, we add the // OID (9 bytes) and its length (2 bytes) to get the position of the DigestInfo sequence, // which is expected to have a length of 0x31 when the NULL param is present or 0x2f if not. - if (bytes1(_unsafeReadBytes32(buffer, length - 50)) == 0x31) { + if (bytes1(_unsafeReadBytes32(buffer, length - 0x32)) == 0x31) { offset = 0x34; // 00 (1 byte) | SEQUENCE length (0x31) = 3031 (2 bytes) | SEQUENCE length (0x0d) = 300d (2 bytes) | OBJECT_IDENTIFIER length (0x09) = 0609 (2 bytes) // SHA256 OID = 608648016503040201 (9 bytes) | NULL = 0500 (2 bytes) (explicit) | OCTET_STRING length (0x20) = 0420 (2 bytes) params = 0x003031300d060960864801650304020105000420000000000000000000000000; mask = 0xffffffffffffffffffffffffffffffffffffffff000000000000000000000000; // (20 bytes) - } else if (bytes1(_unsafeReadBytes32(buffer, length - 48)) == 0x2F) { + } else if (bytes1(_unsafeReadBytes32(buffer, length - 0x30)) == 0x2F) { offset = 0x32; // 00 (1 byte) | SEQUENCE length (0x2f) = 302f (2 bytes) | SEQUENCE length (0x0b) = 300b (2 bytes) | OBJECT_IDENTIFIER length (0x09) = 0609 (2 bytes) // SHA256 OID = 608648016503040201 (9 bytes) | NULL = | OCTET_STRING length (0x20) = 0420 (2 bytes) @@ -111,7 +112,7 @@ library RSA { return false; } - // Length is at least 0x40 and offset is at most 0x34, so this is safe. There is always some padding. + // Length is at least 0x100 and offset is at most 0x34, so this is safe. There is always some padding. uint256 paddingEnd = length - offset; // The padding has variable (arbitrary) length, so we check it byte per byte in a loop. @@ -137,7 +138,7 @@ library RSA { /// @dev Reads a bytes32 from a bytes array without bounds checking. function _unsafeReadBytes32(bytes memory array, uint256 offset) private pure returns (bytes32 result) { // Memory safeness is guaranteed as long as the provided `array` is a Solidity-allocated bytes array - // and `offset` is within bounds. This is the case for all calls to this private function from {pkcs1}. + // and `offset` is within bounds. This is the case for all calls to this private function from {pkcs1Sha256}. assembly ("memory-safe") { result := mload(add(add(array, 0x20), offset)) } diff --git a/contracts/utils/structs/CircularBuffer.sol b/contracts/utils/structs/CircularBuffer.sol index 1d0adf8196b..faaad2f028b 100644 --- a/contracts/utils/structs/CircularBuffer.sol +++ b/contracts/utils/structs/CircularBuffer.sol @@ -49,8 +49,9 @@ library CircularBuffer { * directly. Use the functions provided below instead. Modifying the struct manually may violate assumptions and * lead to unexpected behavior. * - * The last item is at data[(index - 1) % data.length] and the last item is at data[index % data.length]. This - * range can wrap around. + * In a full buffer: + * - The most recently pushed item (last) is at data[(index - 1) % data.length] + * - The oldest item (first) is at data[index % data.length] */ struct Bytes32CircularBuffer { uint256 _count; diff --git a/docs/modules/ROOT/pages/utilities.adoc b/docs/modules/ROOT/pages/utilities.adoc index b8afec4eabd..7b2edf11287 100644 --- a/docs/modules/ROOT/pages/utilities.adoc +++ b/docs/modules/ROOT/pages/utilities.adoc @@ -91,7 +91,7 @@ function _verify( bytes memory e, bytes memory n ) internal pure returns (bool) { - return data.pkcs1(signature, e, n); + return data.pkcs1Sha256(signature, e, n); } ---- diff --git a/test/utils/cryptography/RSA.test.js b/test/utils/cryptography/RSA.test.js index d717811f1dc..bdf33911fe0 100644 --- a/test/utils/cryptography/RSA.test.js +++ b/test/utils/cryptography/RSA.test.js @@ -1,6 +1,7 @@ const { ethers } = require('hardhat'); const { expect } = require('chai'); const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); +const { bytes, bytes32 } = ethers.Typed; const parse = require('./RSA.helper'); @@ -24,7 +25,7 @@ describe('RSA', function () { // const { sha224, sha256 } = require('@noble/hashes/sha256'); // const { sha384, sha512 } = require('@noble/hashes/sha512'); - if (test.SHAAlg === 'SHA256') { + if (test.SHAAlg === 'SHA256' && length >= 0x100) { const result = test.Result === 'P'; it(`signature length ${length} ${test.extra} ${result ? 'works' : 'fails'}`, async function () { @@ -33,65 +34,69 @@ describe('RSA', function () { const exp = '0x' + test.e; const mod = '0x' + test.n; - expect(await this.mock.$pkcs1(ethers.sha256(data), sig, exp, mod)).to.equal(result); - expect(await this.mock.$pkcs1Sha256(data, sig, exp, mod)).to.equal(result); + expect(await this.mock.$pkcs1Sha256(bytes32(ethers.sha256(data)), sig, exp, mod)).to.equal(result); + expect(await this.mock.$pkcs1Sha256(bytes(data), sig, exp, mod)).to.equal(result); }); } } }); describe('others tests', function () { - it('openssl', async function () { - const data = ethers.toUtf8Bytes('hello world'); - const sig = - '0x079bed733b48d69bdb03076cb17d9809072a5a765460bc72072d687dba492afe951d75b814f561f253ee5cc0f3d703b6eab5b5df635b03a5437c0a5c179309812f5b5c97650361c645bc99f806054de21eb187bc0a704ed38d3d4c2871a117c19b6da7e9a3d808481c46b22652d15b899ad3792da5419e50ee38759560002388'; - const exp = - '0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010001'; - const mod = - '0xdf3edde009b96bc5b03b48bd73fe70a3ad20eaf624d0dc1ba121a45cc739893741b7cf82acf1c91573ec8266538997c6699760148de57e54983191eca0176f518e547b85fe0bb7d9e150df19eee734cf5338219c7f8f7b13b39f5384179f62c135e544cb70be7505751f34568e06981095aeec4f3a887639718a3e11d48c240d'; - expect(await this.mock.$pkcs1Sha256(data, sig, exp, mod)).to.be.true; - }); + // > openssl genrsa -out private.pem 2048 + // > openssl rsa -in private.pem -outform der -pubout -out public.pem + // > openssl asn1parse -in public.pem -inform DER -strparse 19 + // > echo -n 'hello world!' | openssl dgst -sha256 -sign private.pem | xxd -p | tr -d \\n + const openssl = { + descr: 'openssl', + data: ethers.toUtf8Bytes('hello world!'), + sig: '0x2ff4349940bf0db9bce422e316ac47e3d24b0a869acb05c9c46f74e17491177698b150f2a5996a6bf7d7c73e05af91ad78632115a7d95b823c462596486e56e8473b75a270ca4760cd83f244d5d3af81d2c7d188879abbc2992b22d51e22ffb725f0828c852ee44f81def383e0f92ebfa3c6d97ca5e52a4254f9a886680e3fb394c2a8a955849313dce2cb416f8a67974effd9a17d229146ce10a98684fb3d46a1e53ddaf831cdd2beed895532533c554ae087b2738a5c4cf0802e8062b2a599fd76d67b92eabffa8a92b24e08fbc866217502a4a3d9f6157e491bede3c1048fa8f2d804f66128e8a883018b0ec33a59e1086bf71ae5dc193d9815ca82892dbc', + exp: '0x010001', + mod: '0xDC1CE5F7B202464CD320B4F9E44FEE0A358BE7022AB155A5BDEE45B1AED3C5A19645D898E294CBA96EAD6929FD8FB4B23E9ADB4D3143A736232C32A8617A77B89F7D8399B9BE37F8349D111067F71D2F20237B9F1A7C1CF44819F9FA5AA030F563DCFB1CC59FFAA86BA2ABEE28D949FED0DF34071B7558950079E28CD9BBA4CAC2F0F86D7BBFB13363C792B5A70C9B279F0B43A264A7CB1A7C7C41FC6EC1D1C1125A6BECE3207AE582F74CE896B9AC18DB00C8985B70145217B831CC313FC06581E186BF70A2EEE2C3C065B5C91A89B2C099B4924CDBF5707D161BD83AC8D9FCA309AC75D63EACF21027C2C9C9F05994331CBDFDD24F9BC6C8B58D8F1824540B', + result: true, + }; // According to RFC4055, pg.5 and RFC8017, pg. 64, for SHA-1, and the SHA-2 family, // the algorithm parameter has to be NULL and both explicit NULL parameter and implicit // NULL parameter (ie, absent NULL parameter) are considered to be legal and equivalent. - it('rfc8017 implicit null parameter', async function () { - const data = ethers.toUtf8Bytes('hello world!'); - const sig = - '0xa0073057133ff3758e7e111b4d7441f1d8cbe4b2dd5ee4316a14264290dee5ed7f175716639bd9bb43a14e4f9fcb9e84dedd35e2205caac04828b2c053f68176d971ea88534dd2eeec903043c3469fc69c206b2a8694fd262488441ed8852280c3d4994e9d42bd1d575c7024095f1a20665925c2175e089c0d731471f6cc145404edf5559fd2276e45e448086f71c78d0cc6628fad394a34e51e8c10bc39bfe09ed2f5f742cc68bee899d0a41e4c75b7b80afd1c321d89ccd9fe8197c44624d91cc935dfa48de3c201099b5b417be748aef29248527e8bbb173cab76b48478d4177b338fe1f1244e64d7d23f07add560d5ad50b68d6649a49d7bc3db686daaa7'; - const exp = '0x03'; - const mod = - '0xe932ac92252f585b3a80a4dd76a897c8b7652952fe788f6ec8dd640587a1ee5647670a8ad4c2be0f9fa6e49c605adf77b5174230af7bd50e5d6d6d6d28ccf0a886a514cc72e51d209cc772a52ef419f6a953f3135929588ebe9b351fca61ced78f346fe00dbb6306e5c2a4c6dfc3779af85ab417371cf34d8387b9b30ae46d7a5ff5a655b8d8455f1b94ae736989d60a6f2fd5cadbffbd504c5a756a2e6bb5cecc13bca7503f6df8b52ace5c410997e98809db4dc30d943de4e812a47553dce54844a78e36401d13f77dc650619fed88d8b3926e3d8e319c80c744779ac5d6abe252896950917476ece5e8fc27d5f053d6018d91b502c4787558a002b9283da7'; - expect(await this.mock.$pkcs1Sha256(data, sig, exp, mod)).to.be.true; - }); + const rfc4055 = { + descr: 'rfc8017 implicit null parameter', + data: ethers.toUtf8Bytes('hello world!'), + sig: '0xa0073057133ff3758e7e111b4d7441f1d8cbe4b2dd5ee4316a14264290dee5ed7f175716639bd9bb43a14e4f9fcb9e84dedd35e2205caac04828b2c053f68176d971ea88534dd2eeec903043c3469fc69c206b2a8694fd262488441ed8852280c3d4994e9d42bd1d575c7024095f1a20665925c2175e089c0d731471f6cc145404edf5559fd2276e45e448086f71c78d0cc6628fad394a34e51e8c10bc39bfe09ed2f5f742cc68bee899d0a41e4c75b7b80afd1c321d89ccd9fe8197c44624d91cc935dfa48de3c201099b5b417be748aef29248527e8bbb173cab76b48478d4177b338fe1f1244e64d7d23f07add560d5ad50b68d6649a49d7bc3db686daaa7', + exp: '0x03', + mod: '0xe932ac92252f585b3a80a4dd76a897c8b7652952fe788f6ec8dd640587a1ee5647670a8ad4c2be0f9fa6e49c605adf77b5174230af7bd50e5d6d6d6d28ccf0a886a514cc72e51d209cc772a52ef419f6a953f3135929588ebe9b351fca61ced78f346fe00dbb6306e5c2a4c6dfc3779af85ab417371cf34d8387b9b30ae46d7a5ff5a655b8d8455f1b94ae736989d60a6f2fd5cadbffbd504c5a756a2e6bb5cecc13bca7503f6df8b52ace5c410997e98809db4dc30d943de4e812a47553dce54844a78e36401d13f77dc650619fed88d8b3926e3d8e319c80c744779ac5d6abe252896950917476ece5e8fc27d5f053d6018d91b502c4787558a002b9283da7', + result: true, + }; - it('returns false for a very short n', async function () { - const data = ethers.toUtf8Bytes('hello world!'); - const sig = '0x0102'; - const exp = '0x03'; - const mod = '0x0405'; - expect(await this.mock.$pkcs1Sha256(data, sig, exp, mod)).to.be.false; - }); + const shortN = { + descr: 'returns false for a very short n', + data: ethers.toUtf8Bytes('hello world!'), + sig: '0x0102', + exp: '0x03', + mod: '0x0405', + result: false, + }; - it('returns false for a signature with different length to n', async function () { - const data = ethers.toUtf8Bytes('hello world!'); - const sig = '0x00112233'; - const exp = '0x03'; - const mod = - '0xe932ac92252f585b3a80a4dd76a897c8b7652952fe788f6ec8dd640587a1ee5647670a8ad4c2be0f9fa6e49c605adf77b5174230af7bd50e5d6d6d6d28ccf0a886a514cc72e51d209cc772a52ef419f6a953f3135929588ebe9b351fca61ced78f346fe00dbb6306e5c2a4c6dfc3779af85ab417371cf34d8387b9b30ae46d7a5ff5a655b8d8455f1b94ae736989d60a6f2fd5cadbffbd504c5a756a2e6bb5cecc13bca7503f6df8b52ace5c410997e98809db4dc30d943de4e812a47553dce54844a78e36401d13f77dc650619fed88d8b3926e3d8e319c80c744779ac5d6abe252896950917476ece5e8fc27d5f053d6018d91b502c4787558a002b9283da7'; - expect(await this.mock.$pkcs1Sha256(data, sig, exp, mod)).to.be.false; - }); + const differentLength = { + descr: 'returns false for a signature with different length to n', + data: ethers.toUtf8Bytes('hello world!'), + sig: '0x00112233', + exp: '0x03', + mod: '0xe932ac92252f585b3a80a4dd76a897c8b7652952fe788f6ec8dd640587a1ee5647670a8ad4c2be0f9fa6e49c605adf77b5174230af7bd50e5d6d6d6d28ccf0a886a514cc72e51d209cc772a52ef419f6a953f3135929588ebe9b351fca61ced78f346fe00dbb6306e5c2a4c6dfc3779af85ab417371cf34d8387b9b30ae46d7a5ff5a655b8d8455f1b94ae736989d60a6f2fd5cadbffbd504c5a756a2e6bb5cecc13bca7503f6df8b52ace5c410997e98809db4dc30d943de4e812a47553dce54844a78e36401d13f77dc650619fed88d8b3926e3d8e319c80c744779ac5d6abe252896950917476ece5e8fc27d5f053d6018d91b502c4787558a002b9283da7', + result: false, + }; - it('returns false if s >= n', async function () { - // this is the openssl example where sig has been replaced by sig + mod - const data = ethers.toUtf8Bytes('hello world'); - const sig = - '0xe6dacb53450242618b3e502a257c08acb44b456c7931988da84f0cda8182b435d6d5453ac1e72b07c7dadf2747609b7d544d15f3f14081f9dbad9c48b7aa78d2bdafd81d630f19a0270d7911f4ec82b171e9a95889ffc9e740dc9fac89407a82d152ecb514967d4d9165e67ce0d7f39a3082657cdfca148a5fc2b3a7348c4795'; - const exp = - '0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010001'; - const mod = - '0xdf3edde009b96bc5b03b48bd73fe70a3ad20eaf624d0dc1ba121a45cc739893741b7cf82acf1c91573ec8266538997c6699760148de57e54983191eca0176f518e547b85fe0bb7d9e150df19eee734cf5338219c7f8f7b13b39f5384179f62c135e544cb70be7505751f34568e06981095aeec4f3a887639718a3e11d48c240d'; - expect(await this.mock.$pkcs1Sha256(data, sig, exp, mod)).to.be.false; - }); + // this is the openssl example where sig has been replaced by sig + mod + const sTooLarge = { + ...openssl, + descr: 'returns false if s >= n', + sig: ethers.toBeHex(ethers.toBigInt(openssl.sig) + ethers.toBigInt(openssl.mod)), + result: false, + }; + + for (const { descr, data, sig, exp, mod, result } of [openssl, rfc4055, shortN, differentLength, sTooLarge]) { + it(descr, async function () { + expect(await this.mock.$pkcs1Sha256(bytes(data), sig, exp, mod)).to.equal(result); + }); + } }); }); From cc67e0eb83a0aef1e0f3e44491344f93cf67ff2c Mon Sep 17 00:00:00 2001 From: PurrProof <149718167+PurrProof@users.noreply.github.com> Date: Wed, 25 Sep 2024 17:34:12 +0200 Subject: [PATCH 13/84] Add comment and tests for zero address behavior in Ownable2Step.transferOwnership() (#5226) Co-authored-by: Hadrien Croubois --- contracts/access/Ownable2Step.sol | 2 ++ test/access/Ownable2Step.test.js | 17 +++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/contracts/access/Ownable2Step.sol b/contracts/access/Ownable2Step.sol index 46765c4daa8..8050ff2b030 100644 --- a/contracts/access/Ownable2Step.sol +++ b/contracts/access/Ownable2Step.sol @@ -37,6 +37,8 @@ abstract contract Ownable2Step is Ownable { /** * @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one. * Can only be called by the current owner. + * + * Setting `newOwner` to the zero address is allowed; this can be used to cancel an initiated ownership transfer. */ function transferOwnership(address newOwner) public virtual override onlyOwner { _pendingOwner = newOwner; diff --git a/test/access/Ownable2Step.test.js b/test/access/Ownable2Step.test.js index 4c49e217015..5620a249132 100644 --- a/test/access/Ownable2Step.test.js +++ b/test/access/Ownable2Step.test.js @@ -81,5 +81,22 @@ describe('Ownable2Step', function () { expect(await this.ownable2Step.owner()).to.equal(this.accountA); }); + + it('allows the owner to cancel an initiated ownership transfer by setting newOwner to zero address', async function () { + // initiate ownership transfer to accountA + await this.ownable2Step.connect(this.owner).transferOwnership(this.accountA); + expect(await this.ownable2Step.pendingOwner()).to.equal(this.accountA); + + // cancel the ownership transfer by setting newOwner to zero address + await expect(this.ownable2Step.connect(this.owner).transferOwnership(ethers.ZeroAddress)) + .to.emit(this.ownable2Step, 'OwnershipTransferStarted') + .withArgs(this.owner, ethers.ZeroAddress); + expect(await this.ownable2Step.pendingOwner()).to.equal(ethers.ZeroAddress); + + // verify that accountA cannot accept ownership anymore + await expect(this.ownable2Step.connect(this.accountA).acceptOwnership()) + .to.be.revertedWithCustomError(this.ownable2Step, 'OwnableUnauthorizedAccount') + .withArgs(this.accountA); + }); }); }); From f6db28630c05d995017d91e4d21009ed637a6299 Mon Sep 17 00:00:00 2001 From: Eric Lau Date: Wed, 25 Sep 2024 17:53:54 -0400 Subject: [PATCH 14/84] Add P256 and RSA documentation sections (#5227) --- contracts/utils/README.adoc | 4 ++++ contracts/utils/cryptography/P256.sol | 8 ++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/contracts/utils/README.adoc b/contracts/utils/README.adoc index 0ef3e5387c8..4b40a967efd 100644 --- a/contracts/utils/README.adoc +++ b/contracts/utils/README.adoc @@ -58,6 +58,10 @@ Because Solidity does not support generic types, {EnumerableMap} and {Enumerable {{ECDSA}} +{{P256}} + +{{RSA}} + {{EIP712}} {{MessageHashUtils}} diff --git a/contracts/utils/cryptography/P256.sol b/contracts/utils/cryptography/P256.sol index 60c5fec3d8b..cd612af6a1c 100644 --- a/contracts/utils/cryptography/P256.sol +++ b/contracts/utils/cryptography/P256.sol @@ -242,10 +242,10 @@ library P256 { * @dev Compute G·u1 + P·u2 using the precomputed points for G and P (see {_preComputeJacobianPoints}). * * Uses Strauss Shamir trick for EC multiplication - * https://stackoverflow.com/questions/50993471/ec-scalar-multiplication-with-strauss-shamir-method - * we optimise on this a bit to do with 2 bits at a time rather than a single bit - * the individual points for a single pass are precomputed - * overall this reduces the number of additions while keeping the same number of doublings + * https://stackoverflow.com/questions/50993471/ec-scalar-multiplication-with-strauss-shamir-method. + * We optimise on this a bit to do with 2 bits at a time rather than a single bit. + * The individual points for a single pass are precomputed. + * Overall this reduces the number of additions while keeping the same number of doublings. */ function _jMultShamir(JPoint[16] memory points, uint256 u1, uint256 u2) private view returns (uint256, uint256) { uint256 x = 0; From 4c481d6584d9a2f32cd347ea07aceed02ee875fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Wed, 25 Sep 2024 16:18:40 -0600 Subject: [PATCH 15/84] Implement 5.1 Full Audit Naming Suggestions (#5215) Co-authored-by: Hadrien Croubois Co-authored-by: cairo --- contracts/utils/structs/Heap.sol | 62 +++++++++++++------------- contracts/utils/structs/MerkleTree.sol | 20 ++++----- 2 files changed, 42 insertions(+), 40 deletions(-) diff --git a/contracts/utils/structs/Heap.sol b/contracts/utils/structs/Heap.sol index c8e75f240a1..17a778f3aa7 100644 --- a/contracts/utils/structs/Heap.sol +++ b/contracts/utils/structs/Heap.sol @@ -13,13 +13,13 @@ import {StorageSlot} from "../StorageSlot.sol"; * @dev Library for managing https://en.wikipedia.org/wiki/Binary_heap[binary heap] that can be used as * https://en.wikipedia.org/wiki/Priority_queue[priority queue]. * - * Heaps are represented as an tree of values where the first element (index 0) is the root, and where the node at - * index i is the child of the node at index (i-1)/2 and the father of nodes at index 2*i+1 and 2*i+2. Each node + * Heaps are represented as a tree of values where the first element (index 0) is the root, and where the node at + * index i is the child of the node at index (i-1)/2 and the parent of nodes at index 2*i+1 and 2*i+2. Each node * stores an element of the heap. * * The structure is ordered so that each node is bigger than its parent. An immediate consequence is that the * highest priority value is the one at the root. This value can be looked up in constant time (O(1)) at - * `heap.tree[0].value` + * `heap.tree[0]` * * The structure is designed to perform the following operations with the corresponding complexities: * @@ -42,7 +42,7 @@ library Heap { /** * @dev Binary heap that supports values of type uint256. * - * Each element of that structure uses 2 storage slots. + * Each element of that structure uses one storage slot. */ struct Uint256Heap { uint256[] tree; @@ -184,7 +184,7 @@ library Heap { * @dev Perform heap maintenance on `self`, starting at `index` (with the `value`), using `comp` as a * comparator, and moving toward the leaves of the underlying tree. * - * NOTE: This is a private function that is called in a trusted context with already cached parameters. `length` + * NOTE: This is a private function that is called in a trusted context with already cached parameters. `size` * and `value` could be extracted from `self` and `index`, but that would require redundant storage read. These * parameters are not verified. It is the caller role to make sure the parameters are correct. */ @@ -195,31 +195,33 @@ library Heap { uint256 value, function(uint256, uint256) view returns (bool) comp ) private { - // Check if there is a risk of overflow when computing the indices of the child nodes. If that is the case, - // there cannot be child nodes in the tree, so sifting is done. - if (index >= type(uint256).max / 2) return; - - // Compute the indices of the potential child nodes - uint256 lIndex = 2 * index + 1; - uint256 rIndex = 2 * index + 2; - - // Three cases: - // 1. Both children exist: sifting may continue on one of the branch (selection required) - // 2. Only left child exist: sifting may contineu on the left branch (no selection required) - // 3. Neither child exist: sifting is done - if (rIndex < size) { - uint256 lValue = self.tree.unsafeAccess(lIndex).value; - uint256 rValue = self.tree.unsafeAccess(rIndex).value; - if (comp(lValue, value) || comp(rValue, value)) { - uint256 cIndex = comp(lValue, rValue).ternary(lIndex, rIndex); - _swap(self, index, cIndex); - _siftDown(self, size, cIndex, value, comp); - } - } else if (lIndex < size) { - uint256 lValue = self.tree.unsafeAccess(lIndex).value; - if (comp(lValue, value)) { - _swap(self, index, lIndex); - _siftDown(self, size, lIndex, value, comp); + unchecked { + // Check if there is a risk of overflow when computing the indices of the child nodes. If that is the case, + // there cannot be child nodes in the tree, so sifting is done. + if (index >= type(uint256).max / 2) return; + + // Compute the indices of the potential child nodes + uint256 lIndex = 2 * index + 1; + uint256 rIndex = 2 * index + 2; + + // Three cases: + // 1. Both children exist: sifting may continue on one of the branch (selection required) + // 2. Only left child exist: sifting may continue on the left branch (no selection required) + // 3. Neither child exist: sifting is done + if (rIndex < size) { + uint256 lValue = self.tree.unsafeAccess(lIndex).value; + uint256 rValue = self.tree.unsafeAccess(rIndex).value; + if (comp(lValue, value) || comp(rValue, value)) { + uint256 cIndex = comp(lValue, rValue).ternary(lIndex, rIndex); + _swap(self, index, cIndex); + _siftDown(self, size, cIndex, value, comp); + } + } else if (lIndex < size) { + uint256 lValue = self.tree.unsafeAccess(lIndex).value; + if (comp(lValue, value)) { + _swap(self, index, lIndex); + _siftDown(self, size, lIndex, value, comp); + } } } } diff --git a/contracts/utils/structs/MerkleTree.sol b/contracts/utils/structs/MerkleTree.sol index c3768ed8c74..6a78c4b3c97 100644 --- a/contracts/utils/structs/MerkleTree.sol +++ b/contracts/utils/structs/MerkleTree.sol @@ -49,7 +49,7 @@ library MerkleTree { /** * @dev Initialize a {Bytes32PushTree} using {Hashes-commutativeKeccak256} to hash internal nodes. - * The capacity of the tree (i.e. number of leaves) is set to `2**levels`. + * The capacity of the tree (i.e. number of leaves) is set to `2**treeDepth`. * * Calling this function on MerkleTree that was already setup and used will reset it to a blank state. * @@ -59,8 +59,8 @@ library MerkleTree { * IMPORTANT: The zero value should be carefully chosen since it will be stored in the tree representing * empty leaves. It should be a value that is not expected to be part of the tree. */ - function setup(Bytes32PushTree storage self, uint8 levels, bytes32 zero) internal returns (bytes32 initialRoot) { - return setup(self, levels, zero, Hashes.commutativeKeccak256); + function setup(Bytes32PushTree storage self, uint8 treeDepth, bytes32 zero) internal returns (bytes32 initialRoot) { + return setup(self, treeDepth, zero, Hashes.commutativeKeccak256); } /** @@ -77,17 +77,17 @@ library MerkleTree { */ function setup( Bytes32PushTree storage self, - uint8 levels, + uint8 treeDepth, bytes32 zero, function(bytes32, bytes32) view returns (bytes32) fnHash ) internal returns (bytes32 initialRoot) { // Store depth in the dynamic array - Arrays.unsafeSetLength(self._sides, levels); - Arrays.unsafeSetLength(self._zeros, levels); + Arrays.unsafeSetLength(self._sides, treeDepth); + Arrays.unsafeSetLength(self._zeros, treeDepth); // Build each root of zero-filled subtrees bytes32 currentZero = zero; - for (uint32 i = 0; i < levels; ++i) { + for (uint32 i = 0; i < treeDepth; ++i) { Arrays.unsafeAccess(self._zeros, i).value = currentZero; currentZero = fnHash(currentZero, currentZero); } @@ -129,20 +129,20 @@ library MerkleTree { function(bytes32, bytes32) view returns (bytes32) fnHash ) internal returns (uint256 index, bytes32 newRoot) { // Cache read - uint256 levels = self._zeros.length; + uint256 treeDepth = depth(self); // Get leaf index index = self._nextLeafIndex++; // Check if tree is full. - if (index >= 1 << levels) { + if (index >= 1 << treeDepth) { Panic.panic(Panic.RESOURCE_ERROR); } // Rebuild branch from leaf to root uint256 currentIndex = index; bytes32 currentLevelHash = leaf; - for (uint32 i = 0; i < levels; i++) { + for (uint32 i = 0; i < treeDepth; i++) { // Reaching the parent node, is currentLevelHash the left child? bool isLeft = currentIndex % 2 == 0; From 414cb9e6fd8c1636c481da404caa9395cf1b91d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Wed, 25 Sep 2024 16:23:31 -0600 Subject: [PATCH 16/84] Consistently name multiple returned values (#5177) --- contracts/governance/Governor.sol | 2 +- .../governance/extensions/GovernorStorage.sol | 22 +++- contracts/governance/utils/Votes.sol | 2 +- contracts/metatx/ERC2771Forwarder.sol | 2 +- contracts/token/ERC20/extensions/ERC4626.sol | 2 +- contracts/token/common/ERC2981.sol | 5 +- contracts/utils/cryptography/ECDSA.sol | 13 ++- contracts/utils/cryptography/P256.sol | 12 +- contracts/utils/structs/Checkpoints.sol | 36 +++++- contracts/utils/structs/EnumerableMap.sol | 110 +++++++++--------- contracts/utils/types/Time.sol | 9 +- scripts/generate/templates/Checkpoints.js | 12 +- scripts/generate/templates/EnumerableMap.js | 26 ++--- 13 files changed, 156 insertions(+), 97 deletions(-) diff --git a/contracts/governance/Governor.sol b/contracts/governance/Governor.sol index 453f2029088..02adffcb39b 100644 --- a/contracts/governance/Governor.sol +++ b/contracts/governance/Governor.sol @@ -802,7 +802,7 @@ abstract contract Governor is Context, ERC165, EIP712, Nonces, IGovernor, IERC72 * @dev Try to parse a character from a string as a hex value. Returns `(true, value)` if the char is in * `[0-9a-fA-F]` and `(false, 0)` otherwise. Value is guaranteed to be in the range `0 <= value < 16` */ - function _tryHexToUint(bytes1 char) private pure returns (bool, uint8) { + function _tryHexToUint(bytes1 char) private pure returns (bool isHex, uint8 value) { uint8 c = uint8(char); unchecked { // Case 0-9 diff --git a/contracts/governance/extensions/GovernorStorage.sol b/contracts/governance/extensions/GovernorStorage.sol index 2547b553bc8..40f7b23d5ac 100644 --- a/contracts/governance/extensions/GovernorStorage.sol +++ b/contracts/governance/extensions/GovernorStorage.sol @@ -88,7 +88,12 @@ abstract contract GovernorStorage is Governor { */ function proposalDetails( uint256 proposalId - ) public view virtual returns (address[] memory, uint256[] memory, bytes[] memory, bytes32) { + ) + public + view + virtual + returns (address[] memory targets, uint256[] memory values, bytes[] memory calldatas, bytes32 descriptionHash) + { // here, using memory is more efficient than storage ProposalDetails memory details = _proposalDetails[proposalId]; if (details.descriptionHash == 0) { @@ -102,14 +107,19 @@ abstract contract GovernorStorage is Governor { */ function proposalDetailsAt( uint256 index - ) public view virtual returns (uint256, address[] memory, uint256[] memory, bytes[] memory, bytes32) { - uint256 proposalId = _proposalIds[index]; - ( + ) + public + view + virtual + returns ( + uint256 proposalId, address[] memory targets, uint256[] memory values, bytes[] memory calldatas, bytes32 descriptionHash - ) = proposalDetails(proposalId); - return (proposalId, targets, values, calldatas, descriptionHash); + ) + { + proposalId = _proposalIds[index]; + (targets, values, calldatas, descriptionHash) = proposalDetails(proposalId); } } diff --git a/contracts/governance/utils/Votes.sol b/contracts/governance/utils/Votes.sol index 2b4def2e0cb..9c3262b9789 100644 --- a/contracts/governance/utils/Votes.sol +++ b/contracts/governance/utils/Votes.sol @@ -232,7 +232,7 @@ abstract contract Votes is Context, EIP712, Nonces, IERC5805 { Checkpoints.Trace208 storage store, function(uint208, uint208) view returns (uint208) op, uint208 delta - ) private returns (uint208, uint208) { + ) private returns (uint208 oldValue, uint208 newValue) { return store.push(clock(), op(store.latest(), delta)); } diff --git a/contracts/metatx/ERC2771Forwarder.sol b/contracts/metatx/ERC2771Forwarder.sol index b66e7894b2c..0b0d185d87e 100644 --- a/contracts/metatx/ERC2771Forwarder.sol +++ b/contracts/metatx/ERC2771Forwarder.sol @@ -218,7 +218,7 @@ contract ERC2771Forwarder is EIP712, Nonces { */ function _recoverForwardRequestSigner( ForwardRequestData calldata request - ) internal view virtual returns (bool, address) { + ) internal view virtual returns (bool isValid, address signer) { (address recovered, ECDSA.RecoverError err, ) = _hashTypedDataV4( keccak256( abi.encode( diff --git a/contracts/token/ERC20/extensions/ERC4626.sol b/contracts/token/ERC20/extensions/ERC4626.sol index c71b14ad48c..73778b530ca 100644 --- a/contracts/token/ERC20/extensions/ERC4626.sol +++ b/contracts/token/ERC20/extensions/ERC4626.sol @@ -83,7 +83,7 @@ abstract contract ERC4626 is ERC20, IERC4626 { /** * @dev Attempts to fetch the asset decimals. A return value of false indicates that the attempt failed in some way. */ - function _tryGetAssetDecimals(IERC20 asset_) private view returns (bool, uint8) { + function _tryGetAssetDecimals(IERC20 asset_) private view returns (bool ok, uint8 assetDecimals) { (bool success, bytes memory encodedDecimals) = address(asset_).staticcall( abi.encodeCall(IERC20Metadata.decimals, ()) ); diff --git a/contracts/token/common/ERC2981.sol b/contracts/token/common/ERC2981.sol index 70e8f469144..7f56b275ee3 100644 --- a/contracts/token/common/ERC2981.sol +++ b/contracts/token/common/ERC2981.sol @@ -58,7 +58,10 @@ abstract contract ERC2981 is IERC2981, ERC165 { /** * @inheritdoc IERC2981 */ - function royaltyInfo(uint256 tokenId, uint256 salePrice) public view virtual returns (address, uint256) { + function royaltyInfo( + uint256 tokenId, + uint256 salePrice + ) public view virtual returns (address receiver, uint256 amount) { RoyaltyInfo storage _royaltyInfo = _tokenRoyaltyInfo[tokenId]; address royaltyReceiver = _royaltyInfo.receiver; uint96 royaltyFraction = _royaltyInfo.royaltyFraction; diff --git a/contracts/utils/cryptography/ECDSA.sol b/contracts/utils/cryptography/ECDSA.sol index 3736171bf9f..dc6534a8aa0 100644 --- a/contracts/utils/cryptography/ECDSA.sol +++ b/contracts/utils/cryptography/ECDSA.sol @@ -53,7 +53,10 @@ library ECDSA { * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js] * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers] */ - function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError, bytes32) { + function tryRecover( + bytes32 hash, + bytes memory signature + ) internal pure returns (address recovered, RecoverError err, bytes32 errArg) { if (signature.length == 65) { bytes32 r; bytes32 s; @@ -96,7 +99,11 @@ library ECDSA { * * See https://eips.ethereum.org/EIPS/eip-2098[ERC-2098 short signatures] */ - function tryRecover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address, RecoverError, bytes32) { + function tryRecover( + bytes32 hash, + bytes32 r, + bytes32 vs + ) internal pure returns (address recovered, RecoverError err, bytes32 errArg) { unchecked { bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff); // We do not check for an overflow here since the shift operation results in 0 or 1. @@ -123,7 +130,7 @@ library ECDSA { uint8 v, bytes32 r, bytes32 s - ) internal pure returns (address, RecoverError, bytes32) { + ) internal pure returns (address recovered, RecoverError err, bytes32 errArg) { // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most diff --git a/contracts/utils/cryptography/P256.sol b/contracts/utils/cryptography/P256.sol index cd612af6a1c..eebedfd34f7 100644 --- a/contracts/utils/cryptography/P256.sol +++ b/contracts/utils/cryptography/P256.sol @@ -120,7 +120,7 @@ library P256 { * IMPORTANT: This function disallows signatures where the `s` value is above `N/2` to prevent malleability. * To flip the `s` value, compute `s = N - s` and `v = 1 - v` if (`v = 0 | 1`). */ - function recovery(bytes32 h, uint8 v, bytes32 r, bytes32 s) internal view returns (bytes32, bytes32) { + function recovery(bytes32 h, uint8 v, bytes32 r, bytes32 s) internal view returns (bytes32 x, bytes32 y) { if (!_isProperSignature(r, s) || v > 1) { return (0, 0); } @@ -136,8 +136,8 @@ library P256 { uint256 w = Math.invModPrime(uint256(r), N); uint256 u1 = mulmod(N - (uint256(h) % N), w, N); uint256 u2 = mulmod(uint256(s), w, N); - (uint256 x, uint256 y) = _jMultShamir(points, u1, u2); - return (bytes32(x), bytes32(y)); + (uint256 xU, uint256 yU) = _jMultShamir(points, u1, u2); + return (bytes32(xU), bytes32(yU)); } /** @@ -247,7 +247,11 @@ library P256 { * The individual points for a single pass are precomputed. * Overall this reduces the number of additions while keeping the same number of doublings. */ - function _jMultShamir(JPoint[16] memory points, uint256 u1, uint256 u2) private view returns (uint256, uint256) { + function _jMultShamir( + JPoint[16] memory points, + uint256 u1, + uint256 u2 + ) private view returns (uint256 rx, uint256 ry) { uint256 x = 0; uint256 y = 0; uint256 z = 0; diff --git a/contracts/utils/structs/Checkpoints.sol b/contracts/utils/structs/Checkpoints.sol index 8d1575e162e..39e18a1fe82 100644 --- a/contracts/utils/structs/Checkpoints.sol +++ b/contracts/utils/structs/Checkpoints.sol @@ -36,7 +36,11 @@ library Checkpoints { * IMPORTANT: Never accept `key` as a user input, since an arbitrary `type(uint32).max` key set will disable the * library. */ - function push(Trace224 storage self, uint32 key, uint224 value) internal returns (uint224, uint224) { + function push( + Trace224 storage self, + uint32 key, + uint224 value + ) internal returns (uint224 oldValue, uint224 newValue) { return _insert(self._checkpoints, key, value); } @@ -127,7 +131,11 @@ library Checkpoints { * @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(Checkpoint224[] storage self, uint32 key, uint224 value) private returns (uint224, uint224) { + function _insert( + Checkpoint224[] storage self, + uint32 key, + uint224 value + ) private returns (uint224 oldValue, uint224 newValue) { uint256 pos = self.length; if (pos > 0) { @@ -231,7 +239,11 @@ library Checkpoints { * IMPORTANT: Never accept `key` as a user input, since an arbitrary `type(uint48).max` key set will disable the * library. */ - function push(Trace208 storage self, uint48 key, uint208 value) internal returns (uint208, uint208) { + function push( + Trace208 storage self, + uint48 key, + uint208 value + ) internal returns (uint208 oldValue, uint208 newValue) { return _insert(self._checkpoints, key, value); } @@ -322,7 +334,11 @@ library Checkpoints { * @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(Checkpoint208[] storage self, uint48 key, uint208 value) private returns (uint208, uint208) { + function _insert( + Checkpoint208[] storage self, + uint48 key, + uint208 value + ) private returns (uint208 oldValue, uint208 newValue) { uint256 pos = self.length; if (pos > 0) { @@ -426,7 +442,11 @@ library Checkpoints { * IMPORTANT: Never accept `key` as a user input, since an arbitrary `type(uint96).max` key set will disable the * library. */ - function push(Trace160 storage self, uint96 key, uint160 value) internal returns (uint160, uint160) { + function push( + Trace160 storage self, + uint96 key, + uint160 value + ) internal returns (uint160 oldValue, uint160 newValue) { return _insert(self._checkpoints, key, value); } @@ -517,7 +537,11 @@ library Checkpoints { * @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(Checkpoint160[] storage self, uint96 key, uint160 value) private returns (uint160, uint160) { + function _insert( + Checkpoint160[] storage self, + uint96 key, + uint160 value + ) private returns (uint160 oldValue, uint160 newValue) { uint256 pos = self.length; if (pos > 0) { diff --git a/contracts/utils/structs/EnumerableMap.sol b/contracts/utils/structs/EnumerableMap.sol index e61182e1fbd..d86e57f69d5 100644 --- a/contracts/utils/structs/EnumerableMap.sol +++ b/contracts/utils/structs/EnumerableMap.sol @@ -114,21 +114,21 @@ library EnumerableMap { * * - `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]); + function at(Bytes32ToBytes32Map storage map, uint256 index) internal view returns (bytes32 key, bytes32 value) { + bytes32 atKey = map._keys.at(index); + return (atKey, map._values[atKey]); } /** * @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)) { + function tryGet(Bytes32ToBytes32Map storage map, bytes32 key) internal view returns (bool exists, bytes32 value) { + bytes32 val = map._values[key]; + if (val == bytes32(0)) { return (contains(map, key), bytes32(0)); } else { - return (true, value); + return (true, val); } } @@ -208,18 +208,18 @@ library EnumerableMap { * * - `index` must be strictly less than {length}. */ - function at(UintToUintMap storage map, uint256 index) internal view returns (uint256, uint256) { - (bytes32 key, bytes32 value) = at(map._inner, index); - return (uint256(key), uint256(value)); + function at(UintToUintMap storage map, uint256 index) internal view returns (uint256 key, uint256 value) { + (bytes32 atKey, bytes32 val) = at(map._inner, index); + return (uint256(atKey), uint256(val)); } /** * @dev Tries to returns the value associated with `key`. O(1). * Does not revert if `key` is not in the map. */ - function tryGet(UintToUintMap storage map, uint256 key) internal view returns (bool, uint256) { - (bool success, bytes32 value) = tryGet(map._inner, bytes32(key)); - return (success, uint256(value)); + function tryGet(UintToUintMap storage map, uint256 key) internal view returns (bool exists, uint256 value) { + (bool success, bytes32 val) = tryGet(map._inner, bytes32(key)); + return (success, uint256(val)); } /** @@ -301,18 +301,18 @@ library EnumerableMap { * * - `index` must be strictly less than {length}. */ - function at(UintToAddressMap storage map, uint256 index) internal view returns (uint256, address) { - (bytes32 key, bytes32 value) = at(map._inner, index); - return (uint256(key), address(uint160(uint256(value)))); + function at(UintToAddressMap storage map, uint256 index) internal view returns (uint256 key, address value) { + (bytes32 atKey, bytes32 val) = at(map._inner, index); + return (uint256(atKey), address(uint160(uint256(val)))); } /** * @dev Tries to returns the value associated with `key`. O(1). * Does not revert if `key` is not in the map. */ - function tryGet(UintToAddressMap storage map, uint256 key) internal view returns (bool, address) { - (bool success, bytes32 value) = tryGet(map._inner, bytes32(key)); - return (success, address(uint160(uint256(value)))); + function tryGet(UintToAddressMap storage map, uint256 key) internal view returns (bool exists, address value) { + (bool success, bytes32 val) = tryGet(map._inner, bytes32(key)); + return (success, address(uint160(uint256(val)))); } /** @@ -394,18 +394,18 @@ library EnumerableMap { * * - `index` must be strictly less than {length}. */ - function at(UintToBytes32Map storage map, uint256 index) internal view returns (uint256, bytes32) { - (bytes32 key, bytes32 value) = at(map._inner, index); - return (uint256(key), value); + function at(UintToBytes32Map storage map, uint256 index) internal view returns (uint256 key, bytes32 value) { + (bytes32 atKey, bytes32 val) = at(map._inner, index); + return (uint256(atKey), val); } /** * @dev Tries to returns the value associated with `key`. O(1). * Does not revert if `key` is not in the map. */ - function tryGet(UintToBytes32Map storage map, uint256 key) internal view returns (bool, bytes32) { - (bool success, bytes32 value) = tryGet(map._inner, bytes32(key)); - return (success, value); + function tryGet(UintToBytes32Map storage map, uint256 key) internal view returns (bool exists, bytes32 value) { + (bool success, bytes32 val) = tryGet(map._inner, bytes32(key)); + return (success, val); } /** @@ -487,18 +487,18 @@ library EnumerableMap { * * - `index` must be strictly less than {length}. */ - function at(AddressToUintMap storage map, uint256 index) internal view returns (address, uint256) { - (bytes32 key, bytes32 value) = at(map._inner, index); - return (address(uint160(uint256(key))), uint256(value)); + function at(AddressToUintMap storage map, uint256 index) internal view returns (address key, uint256 value) { + (bytes32 atKey, bytes32 val) = at(map._inner, index); + return (address(uint160(uint256(atKey))), uint256(val)); } /** * @dev Tries to returns the value associated with `key`. O(1). * Does not revert if `key` is not in the map. */ - function tryGet(AddressToUintMap storage map, address key) internal view returns (bool, uint256) { - (bool success, bytes32 value) = tryGet(map._inner, bytes32(uint256(uint160(key)))); - return (success, uint256(value)); + function tryGet(AddressToUintMap storage map, address key) internal view returns (bool exists, uint256 value) { + (bool success, bytes32 val) = tryGet(map._inner, bytes32(uint256(uint160(key)))); + return (success, uint256(val)); } /** @@ -580,18 +580,18 @@ library EnumerableMap { * * - `index` must be strictly less than {length}. */ - function at(AddressToAddressMap storage map, uint256 index) internal view returns (address, address) { - (bytes32 key, bytes32 value) = at(map._inner, index); - return (address(uint160(uint256(key))), address(uint160(uint256(value)))); + function at(AddressToAddressMap storage map, uint256 index) internal view returns (address key, address value) { + (bytes32 atKey, bytes32 val) = at(map._inner, index); + return (address(uint160(uint256(atKey))), address(uint160(uint256(val)))); } /** * @dev Tries to returns the value associated with `key`. O(1). * Does not revert if `key` is not in the map. */ - function tryGet(AddressToAddressMap storage map, address key) internal view returns (bool, address) { - (bool success, bytes32 value) = tryGet(map._inner, bytes32(uint256(uint160(key)))); - return (success, address(uint160(uint256(value)))); + function tryGet(AddressToAddressMap storage map, address key) internal view returns (bool exists, address value) { + (bool success, bytes32 val) = tryGet(map._inner, bytes32(uint256(uint160(key)))); + return (success, address(uint160(uint256(val)))); } /** @@ -673,18 +673,18 @@ library EnumerableMap { * * - `index` must be strictly less than {length}. */ - function at(AddressToBytes32Map storage map, uint256 index) internal view returns (address, bytes32) { - (bytes32 key, bytes32 value) = at(map._inner, index); - return (address(uint160(uint256(key))), value); + function at(AddressToBytes32Map storage map, uint256 index) internal view returns (address key, bytes32 value) { + (bytes32 atKey, bytes32 val) = at(map._inner, index); + return (address(uint160(uint256(atKey))), val); } /** * @dev Tries to returns the value associated with `key`. O(1). * Does not revert if `key` is not in the map. */ - function tryGet(AddressToBytes32Map storage map, address key) internal view returns (bool, bytes32) { - (bool success, bytes32 value) = tryGet(map._inner, bytes32(uint256(uint160(key)))); - return (success, value); + function tryGet(AddressToBytes32Map storage map, address key) internal view returns (bool exists, bytes32 value) { + (bool success, bytes32 val) = tryGet(map._inner, bytes32(uint256(uint160(key)))); + return (success, val); } /** @@ -766,18 +766,18 @@ library EnumerableMap { * * - `index` must be strictly less than {length}. */ - function at(Bytes32ToUintMap storage map, uint256 index) internal view returns (bytes32, uint256) { - (bytes32 key, bytes32 value) = at(map._inner, index); - return (key, uint256(value)); + function at(Bytes32ToUintMap storage map, uint256 index) internal view returns (bytes32 key, uint256 value) { + (bytes32 atKey, bytes32 val) = at(map._inner, index); + return (atKey, uint256(val)); } /** * @dev Tries to returns the value associated with `key`. O(1). * Does not revert if `key` is not in the map. */ - function tryGet(Bytes32ToUintMap storage map, bytes32 key) internal view returns (bool, uint256) { - (bool success, bytes32 value) = tryGet(map._inner, key); - return (success, uint256(value)); + function tryGet(Bytes32ToUintMap storage map, bytes32 key) internal view returns (bool exists, uint256 value) { + (bool success, bytes32 val) = tryGet(map._inner, key); + return (success, uint256(val)); } /** @@ -859,18 +859,18 @@ library EnumerableMap { * * - `index` must be strictly less than {length}. */ - function at(Bytes32ToAddressMap storage map, uint256 index) internal view returns (bytes32, address) { - (bytes32 key, bytes32 value) = at(map._inner, index); - return (key, address(uint160(uint256(value)))); + function at(Bytes32ToAddressMap storage map, uint256 index) internal view returns (bytes32 key, address value) { + (bytes32 atKey, bytes32 val) = at(map._inner, index); + return (atKey, address(uint160(uint256(val)))); } /** * @dev Tries to returns the value associated with `key`. O(1). * Does not revert if `key` is not in the map. */ - function tryGet(Bytes32ToAddressMap storage map, bytes32 key) internal view returns (bool, address) { - (bool success, bytes32 value) = tryGet(map._inner, key); - return (success, address(uint160(uint256(value)))); + function tryGet(Bytes32ToAddressMap storage map, bytes32 key) internal view returns (bool exists, address value) { + (bool success, bytes32 val) = tryGet(map._inner, key); + return (success, address(uint160(uint256(val)))); } /** diff --git a/contracts/utils/types/Time.sol b/contracts/utils/types/Time.sol index 9faef31f054..1f729515425 100644 --- a/contracts/utils/types/Time.sol +++ b/contracts/utils/types/Time.sol @@ -71,8 +71,11 @@ library Time { * @dev Get the value at a given timepoint plus the pending value and effect timepoint if there is a scheduled * change after this timepoint. If the effect timepoint is 0, then the pending value should not be considered. */ - function _getFullAt(Delay self, uint48 timepoint) private pure returns (uint32, uint32, uint48) { - (uint32 valueBefore, uint32 valueAfter, uint48 effect) = self.unpack(); + function _getFullAt( + Delay self, + uint48 timepoint + ) private pure returns (uint32 valueBefore, uint32 valueAfter, uint48 effect) { + (valueBefore, valueAfter, effect) = self.unpack(); return effect <= timepoint ? (valueAfter, 0, 0) : (valueBefore, valueAfter, effect); } @@ -80,7 +83,7 @@ library Time { * @dev Get the current value plus the pending value and effect timepoint if there is a scheduled change. If the * effect timepoint is 0, then the pending value should not be considered. */ - function getFull(Delay self) internal view returns (uint32, uint32, uint48) { + function getFull(Delay self) internal view returns (uint32 valueBefore, uint32 valueAfter, uint48 effect) { return _getFullAt(self, timestamp()); } diff --git a/scripts/generate/templates/Checkpoints.js b/scripts/generate/templates/Checkpoints.js index 1a4d1a7a5cd..d418b1177d1 100644 --- a/scripts/generate/templates/Checkpoints.js +++ b/scripts/generate/templates/Checkpoints.js @@ -41,7 +41,11 @@ struct ${opts.checkpointTypeName} { * 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} oldValue, ${opts.valueTypeName} newValue) { return _insert(self.${opts.checkpointFieldName}, key, value); } @@ -132,7 +136,11 @@ 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} oldValue, ${opts.valueTypeName} newValue) { uint256 pos = self.length; if (pos > 0) { diff --git a/scripts/generate/templates/EnumerableMap.js b/scripts/generate/templates/EnumerableMap.js index 656d8463dec..fc896f8fb9a 100644 --- a/scripts/generate/templates/EnumerableMap.js +++ b/scripts/generate/templates/EnumerableMap.js @@ -117,21 +117,21 @@ function length(Bytes32ToBytes32Map storage map) internal view returns (uint256) * * - \`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]); +function at(Bytes32ToBytes32Map storage map, uint256 index) internal view returns (bytes32 key, bytes32 value) { + bytes32 atKey = map._keys.at(index); + return (atKey, map._values[atKey]); } /** * @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)) { +function tryGet(Bytes32ToBytes32Map storage map, bytes32 key) internal view returns (bool exists, bytes32 value) { + bytes32 val = map._values[key]; + if (val == bytes32(0)) { return (contains(map, key), bytes32(0)); } else { - return (true, value); + return (true, val); } } @@ -213,18 +213,18 @@ function length(${name} storage map) internal view returns (uint256) { * * - \`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')}); +function at(${name} storage map, uint256 index) internal view returns (${keyType} key, ${valueType} value) { + (bytes32 atKey, bytes32 val) = at(map._inner, index); + return (${fromBytes32(keyType, 'atKey')}, ${fromBytes32(valueType, 'val')}); } /** * @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')}); +function tryGet(${name} storage map, ${keyType} key) internal view returns (bool exists, ${valueType} value) { + (bool success, bytes32 val) = tryGet(map._inner, ${toBytes32(keyType, 'key')}); + return (success, ${fromBytes32(valueType, 'val')}); } /** From 057d35a9eb363b4468d9cef69423879e3fa34e82 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 25 Sep 2024 16:28:02 -0600 Subject: [PATCH 17/84] Update dependency halmos to v0.2.0 (#5225) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- fv-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fv-requirements.txt b/fv-requirements.txt index 6dda424ed2b..608b4de24a3 100644 --- a/fv-requirements.txt +++ b/fv-requirements.txt @@ -1,4 +1,4 @@ certora-cli==4.13.1 # File uses a custom name (fv-requirements.txt) so that it isn't picked by Netlify's build # whose latest Python version is 0.3.8, incompatible with most recent versions of Halmos -halmos==0.1.14 +halmos==0.2.0 From ae753b7eac6eaba0e8eebab11ecf2ca5a0057d90 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Fri, 27 Sep 2024 07:48:15 +0200 Subject: [PATCH 18/84] Improve VestingWallet's constructor and RSA.pkcs1Sha256 documentation (#5229) --- contracts/finance/VestingWallet.sol | 4 ++-- contracts/finance/VestingWalletCliff.sol | 3 ++- contracts/utils/cryptography/RSA.sol | 15 ++++++++------- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/contracts/finance/VestingWallet.sol b/contracts/finance/VestingWallet.sol index f472b66068b..153b8fc63de 100644 --- a/contracts/finance/VestingWallet.sol +++ b/contracts/finance/VestingWallet.sol @@ -37,8 +37,8 @@ contract VestingWallet is Context, Ownable { uint64 private immutable _duration; /** - * @dev Sets the sender as the initial owner, the beneficiary as the pending owner, the start timestamp and the - * vesting duration of the vesting wallet. + * @dev Sets the beneficiary (owner), the start timestamp and the vesting duration (in seconds) of the vesting + * wallet. */ constructor(address beneficiary, uint64 startTimestamp, uint64 durationSeconds) payable Ownable(beneficiary) { _start = startTimestamp; diff --git a/contracts/finance/VestingWalletCliff.sol b/contracts/finance/VestingWalletCliff.sol index 034dd9cf6ea..7582ca56876 100644 --- a/contracts/finance/VestingWalletCliff.sol +++ b/contracts/finance/VestingWalletCliff.sol @@ -17,7 +17,8 @@ abstract contract VestingWalletCliff is VestingWallet { error InvalidCliffDuration(uint64 cliffSeconds, uint64 durationSeconds); /** - * @dev Set the start timestamp of the vesting wallet cliff. + * @dev Set the duration of the cliff, in seconds. The cliff starts vesting schedule (see {VestingWallet}'s + * constructor) and ends `cliffSeconds` later. */ constructor(uint64 cliffSeconds) { if (cliffSeconds > duration()) { diff --git a/contracts/utils/cryptography/RSA.sol b/contracts/utils/cryptography/RSA.sol index 6d355c60e2f..689124b5959 100644 --- a/contracts/utils/cryptography/RSA.sol +++ b/contracts/utils/cryptography/RSA.sol @@ -27,14 +27,15 @@ library RSA { /** * @dev Verifies a PKCSv1.5 signature given a digest according to the verification - * method described in https://datatracker.ietf.org/doc/html/rfc8017#section-8.2.2[section 8.2.2 of RFC8017] with support - * for explicit or implicit NULL parameters in the DigestInfo (no other optional parameters are supported). + * method described in https://datatracker.ietf.org/doc/html/rfc8017#section-8.2.2[section 8.2.2 of RFC8017] with + * support for explicit or implicit NULL parameters in the DigestInfo (no other optional parameters are supported). * - * IMPORTANT: For security reason, this function requires the signature and modulus to have a length of at least 2048 bits. - * If you use a smaller key, consider replacing it with a larger, more secure, one. + * IMPORTANT: For security reason, this function requires the signature and modulus to have a length of at least + * 2048 bits. If you use a smaller key, consider replacing it with a larger, more secure, one. * - * WARNING: PKCS#1 v1.5 allows for replayability given the message may contain arbitrary optional parameters in the - * DigestInfo. Consider using an onchain nonce or unique identifier to include in the message to prevent replay attacks. + * WARNING: This verification algorithm doesn't prevent replayability. If called multiple times with the same + * digest, public key and (valid signature), it will return true every time. Consider including an onchain nonce or + * unique identifier in the message to prevent replay attacks. * * @param digest the digest to verify * @param s is a buffer containing the signature @@ -79,7 +80,7 @@ library RSA { // - PS is padding filled with 0xFF // - DigestInfo ::= SEQUENCE { // digestAlgorithm AlgorithmIdentifier, - // [optional algorithm parameters] + // [optional algorithm parameters] -- not currently supported // digest OCTET STRING // } From cceac54953ccda8a9a028d0b9bf4378605fdf67e Mon Sep 17 00:00:00 2001 From: cairo Date: Fri, 27 Sep 2024 08:47:15 -0700 Subject: [PATCH 19/84] Add introduction tag for v5.1 contracts (#5228) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernesto García --- contracts/finance/VestingWalletCliff.sol | 2 ++ .../governance/extensions/GovernorCountingFractional.sol | 2 ++ contracts/token/ERC1155/utils/ERC1155Utils.sol | 2 ++ contracts/token/ERC20/extensions/ERC1363.sol | 2 ++ .../token/ERC20/extensions/draft-ERC20TemporaryApproval.sol | 2 ++ contracts/token/ERC721/utils/ERC721Utils.sol | 2 ++ contracts/utils/Comparators.sol | 5 +++++ contracts/utils/Errors.sol | 2 ++ contracts/utils/Packing.sol | 2 ++ contracts/utils/Panic.sol | 2 ++ contracts/utils/ReentrancyGuardTransient.sol | 2 ++ contracts/utils/SlotDerivation.sol | 2 ++ contracts/utils/cryptography/Hashes.sol | 2 ++ contracts/utils/cryptography/P256.sol | 2 ++ contracts/utils/cryptography/RSA.sol | 2 ++ contracts/utils/structs/CircularBuffer.sol | 2 ++ contracts/utils/structs/Heap.sol | 2 ++ scripts/generate/templates/Packing.js | 2 ++ scripts/generate/templates/SlotDerivation.js | 2 ++ 19 files changed, 41 insertions(+) diff --git a/contracts/finance/VestingWalletCliff.sol b/contracts/finance/VestingWalletCliff.sol index 7582ca56876..83d82510427 100644 --- a/contracts/finance/VestingWalletCliff.sol +++ b/contracts/finance/VestingWalletCliff.sol @@ -7,6 +7,8 @@ import {VestingWallet} from "./VestingWallet.sol"; /** * @dev Extension of {VestingWallet} that adds a cliff to the vesting schedule. + * + * _Available since v5.1._ */ abstract contract VestingWalletCliff is VestingWallet { using SafeCast for *; diff --git a/contracts/governance/extensions/GovernorCountingFractional.sol b/contracts/governance/extensions/GovernorCountingFractional.sol index 3a4fb664a53..a3f40201fd1 100644 --- a/contracts/governance/extensions/GovernorCountingFractional.sol +++ b/contracts/governance/extensions/GovernorCountingFractional.sol @@ -27,6 +27,8 @@ import {Math} from "../../utils/math/Math.sol"; * * Voting privately from a shielded pool using zero knowledge proofs. * * Based on ScopeLift's GovernorCountingFractional[https://github.com/ScopeLift/flexible-voting/blob/e5de2efd1368387b840931f19f3c184c85842761/src/GovernorCountingFractional.sol] + * + * _Available since v5.1._ */ abstract contract GovernorCountingFractional is Governor { using Math for *; diff --git a/contracts/token/ERC1155/utils/ERC1155Utils.sol b/contracts/token/ERC1155/utils/ERC1155Utils.sol index 1439330e41c..62accf6bc6e 100644 --- a/contracts/token/ERC1155/utils/ERC1155Utils.sol +++ b/contracts/token/ERC1155/utils/ERC1155Utils.sol @@ -9,6 +9,8 @@ import {IERC1155Errors} from "../../../interfaces/draft-IERC6093.sol"; * @dev Library that provide common ERC-1155 utility functions. * * See https://eips.ethereum.org/EIPS/eip-1155[ERC-1155]. + * + * _Available since v5.1._ */ library ERC1155Utils { /** diff --git a/contracts/token/ERC20/extensions/ERC1363.sol b/contracts/token/ERC20/extensions/ERC1363.sol index 253e443d6b1..4ae78f32764 100644 --- a/contracts/token/ERC20/extensions/ERC1363.sol +++ b/contracts/token/ERC20/extensions/ERC1363.sol @@ -12,6 +12,8 @@ import {ERC1363Utils} from "../utils/ERC1363Utils.sol"; * @dev Extension of {ERC20} tokens that adds support for code execution after transfers and approvals * on recipient contracts. Calls after transfers are enabled through the {ERC1363-transferAndCall} and * {ERC1363-transferFromAndCall} methods while calls after approvals can be made with {ERC1363-approveAndCall} + * + * _Available since v5.1._ */ abstract contract ERC1363 is ERC20, ERC165, IERC1363 { /** diff --git a/contracts/token/ERC20/extensions/draft-ERC20TemporaryApproval.sol b/contracts/token/ERC20/extensions/draft-ERC20TemporaryApproval.sol index fa1e098a7cc..515b080ea16 100644 --- a/contracts/token/ERC20/extensions/draft-ERC20TemporaryApproval.sol +++ b/contracts/token/ERC20/extensions/draft-ERC20TemporaryApproval.sol @@ -12,6 +12,8 @@ import {StorageSlot} from "../../../utils/StorageSlot.sol"; * @dev Extension of {ERC20} that adds support for temporary allowances following ERC-7674. * * WARNING: This is a draft contract. The corresponding ERC is still subject to changes. + * + * _Available since v5.1._ */ abstract contract ERC20TemporaryApproval is ERC20, IERC7674 { using SlotDerivation for bytes32; diff --git a/contracts/token/ERC721/utils/ERC721Utils.sol b/contracts/token/ERC721/utils/ERC721Utils.sol index 712ac16a70a..0e18f5cc0a8 100644 --- a/contracts/token/ERC721/utils/ERC721Utils.sol +++ b/contracts/token/ERC721/utils/ERC721Utils.sol @@ -9,6 +9,8 @@ import {IERC721Errors} from "../../../interfaces/draft-IERC6093.sol"; * @dev Library that provide common ERC-721 utility functions. * * See https://eips.ethereum.org/EIPS/eip-721[ERC-721]. + * + * _Available since v5.1._ */ library ERC721Utils { /** diff --git a/contracts/utils/Comparators.sol b/contracts/utils/Comparators.sol index 3a63aa0e8ee..c10734712bc 100644 --- a/contracts/utils/Comparators.sol +++ b/contracts/utils/Comparators.sol @@ -2,6 +2,11 @@ pragma solidity ^0.8.20; +/** + * @dev Provides a set of functions to compare values. + * + * _Available since v5.1._ + */ library Comparators { function lt(uint256 a, uint256 b) internal pure returns (bool) { return a < b; diff --git a/contracts/utils/Errors.sol b/contracts/utils/Errors.sol index 633d8c20286..715db953620 100644 --- a/contracts/utils/Errors.sol +++ b/contracts/utils/Errors.sol @@ -7,6 +7,8 @@ pragma solidity ^0.8.20; * * IMPORTANT: Backwards compatibility is not guaranteed in future versions of the library. * It is recommended to avoid relying on the error API for critical functionality. + * + * _Available since v5.1._ */ library Errors { /** diff --git a/contracts/utils/Packing.sol b/contracts/utils/Packing.sol index 8a8e3fee86f..4da311cf3c0 100644 --- a/contracts/utils/Packing.sol +++ b/contracts/utils/Packing.sol @@ -28,6 +28,8 @@ pragma solidity ^0.8.20; * } * } * ``` + * + * _Available since v5.1._ */ // solhint-disable func-name-mixedcase library Packing { diff --git a/contracts/utils/Panic.sol b/contracts/utils/Panic.sol index 8769bd9a786..0e3c38c3e36 100644 --- a/contracts/utils/Panic.sol +++ b/contracts/utils/Panic.sol @@ -18,6 +18,8 @@ pragma solidity ^0.8.20; * ``` * * Follows the list from https://github.com/ethereum/solidity/blob/v0.8.24/libsolutil/ErrorCodes.h[libsolutil]. + * + * _Available since v5.1._ */ // slither-disable-next-line unused-state library Panic { diff --git a/contracts/utils/ReentrancyGuardTransient.sol b/contracts/utils/ReentrancyGuardTransient.sol index 0389b86205d..54df0a71ac8 100644 --- a/contracts/utils/ReentrancyGuardTransient.sol +++ b/contracts/utils/ReentrancyGuardTransient.sol @@ -8,6 +8,8 @@ import {StorageSlot} from "./StorageSlot.sol"; * @dev Variant of {ReentrancyGuard} that uses transient storage. * * NOTE: This variant only works on networks where EIP-1153 is available. + * + * _Available since v5.1._ */ abstract contract ReentrancyGuardTransient { using StorageSlot for *; diff --git a/contracts/utils/SlotDerivation.sol b/contracts/utils/SlotDerivation.sol index c248edc01c1..83b9d5639c0 100644 --- a/contracts/utils/SlotDerivation.sol +++ b/contracts/utils/SlotDerivation.sol @@ -34,6 +34,8 @@ pragma solidity ^0.8.20; * * NOTE: This library provides a way to manipulate storage locations in a non-standard way. Tooling for checking * upgrade safety will ignore the slots accessed through this library. + * + * _Available since v5.1._ */ library SlotDerivation { /** diff --git a/contracts/utils/cryptography/Hashes.sol b/contracts/utils/cryptography/Hashes.sol index 85efd6d294e..0ed89b308c0 100644 --- a/contracts/utils/cryptography/Hashes.sol +++ b/contracts/utils/cryptography/Hashes.sol @@ -4,6 +4,8 @@ pragma solidity ^0.8.20; /** * @dev Library of standard hash functions. + * + * _Available since v5.1._ */ library Hashes { /** diff --git a/contracts/utils/cryptography/P256.sol b/contracts/utils/cryptography/P256.sol index eebedfd34f7..1c46e38b0be 100644 --- a/contracts/utils/cryptography/P256.sol +++ b/contracts/utils/cryptography/P256.sol @@ -14,6 +14,8 @@ import {Errors} from "../Errors.sol"; * Based on the original https://github.com/itsobvioustech/aa-passkeys-wallet/blob/d3d423f28a4d8dfcb203c7fa0c47f42592a7378e/src/Secp256r1.sol[implementation of itsobvioustech] (GNU General Public License v3.0). * Heavily inspired in https://github.com/maxrobot/elliptic-solidity/blob/c4bb1b6e8ae89534d8db3a6b3a6b52219100520f/contracts/Secp256r1.sol[maxrobot] and * https://github.com/tdrerup/elliptic-curve-solidity/blob/59a9c25957d4d190eff53b6610731d81a077a15e/contracts/curves/EllipticCurve.sol[tdrerup] implementations. + * + * _Available since v5.1._ */ library P256 { struct JPoint { diff --git a/contracts/utils/cryptography/RSA.sol b/contracts/utils/cryptography/RSA.sol index 689124b5959..c8da4f96206 100644 --- a/contracts/utils/cryptography/RSA.sol +++ b/contracts/utils/cryptography/RSA.sol @@ -11,6 +11,8 @@ import {Math} from "../math/Math.sol"; * RSA semantically secure for signing messages. * * Inspired by https://github.com/adria0/SolRsaVerify/blob/79c6182cabb9102ea69d4a2e996816091d5f1cd1[Adrià Massanet's work] (GNU General Public License v3.0). + * + * _Available since v5.1._ */ library RSA { /** diff --git a/contracts/utils/structs/CircularBuffer.sol b/contracts/utils/structs/CircularBuffer.sol index faaad2f028b..cf1afbbd6ea 100644 --- a/contracts/utils/structs/CircularBuffer.sol +++ b/contracts/utils/structs/CircularBuffer.sol @@ -34,6 +34,8 @@ import {Panic} from "../Panic.sol"; * CircularBuffer.Bytes32CircularBuffer private myBuffer; * } * ``` + * + * _Available since v5.1._ */ library CircularBuffer { /** diff --git a/contracts/utils/structs/Heap.sol b/contracts/utils/structs/Heap.sol index 17a778f3aa7..d33dfbec8b2 100644 --- a/contracts/utils/structs/Heap.sol +++ b/contracts/utils/structs/Heap.sol @@ -33,6 +33,8 @@ import {StorageSlot} from "../StorageSlot.sol"; * IMPORTANT: This library allows for the use of custom comparator functions. Given that manipulating * memory can lead to unexpected behavior. Consider verifying that the comparator does not manipulate * the Heap's state directly and that it follows the Solidity memory safety rules. + * + * _Available since v5.1._ */ library Heap { using Arrays for *; diff --git a/scripts/generate/templates/Packing.js b/scripts/generate/templates/Packing.js index d841c2f816a..9f3b7716a6a 100644 --- a/scripts/generate/templates/Packing.js +++ b/scripts/generate/templates/Packing.js @@ -32,6 +32,8 @@ pragma solidity ^0.8.20; * } * } * \`\`\` + * + * _Available since v5.1._ */ // solhint-disable func-name-mixedcase `; diff --git a/scripts/generate/templates/SlotDerivation.js b/scripts/generate/templates/SlotDerivation.js index 39d0d9e3508..ec4d244b931 100644 --- a/scripts/generate/templates/SlotDerivation.js +++ b/scripts/generate/templates/SlotDerivation.js @@ -36,6 +36,8 @@ pragma solidity ^0.8.20; * * NOTE: This library provides a way to manipulate storage locations in a non-standard way. Tooling for checking * upgrade safety will ignore the slots accessed through this library. + * + * _Available since v5.1._ */ `; From d3ca1d1f006e6e69311d889642716a1ea4ff228b Mon Sep 17 00:00:00 2001 From: plooten Date: Mon, 30 Sep 2024 14:55:03 +0200 Subject: [PATCH 20/84] Fix invalid link and typos (#5232) --- docs/modules/ROOT/pages/utilities.adoc | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/modules/ROOT/pages/utilities.adoc b/docs/modules/ROOT/pages/utilities.adoc index 7b2edf11287..adee6477f95 100644 --- a/docs/modules/ROOT/pages/utilities.adoc +++ b/docs/modules/ROOT/pages/utilities.adoc @@ -32,7 +32,7 @@ WARNING: Getting signature verification right is not trivial: make sure you full ==== P256 Signatures (secp256r1) -P256, also known as secp256r1, is one of the most used signature schemes. P256 signatures are standardized by the National Institute of Standards and Technology (NIST) and it's widely available in consumer hardware and software. +P256, also known as secp256r1, is one of the most used signature schemes. P256 signatures are standardized by the National Institute of Standards and Technology (NIST) and they are widely available in consumer hardware and software. These signatures are different from regular Ethereum Signatures (secp256k1) in that they use a different elliptic curve to perform operations but have similar security guarantees. @@ -51,7 +51,7 @@ function _verify( } ---- -By default, the `verify` function will try calling the (https://github.com/ethereum/RIPs/blob/master/RIPS/rip-7212.md)[RIP-7212] precompile at address `0x100` and will fallback to an implementation in Solidity if not available. We encourage you to use `verifyNative` if you know the precompile is available on the chain you're working on and on any other chain on which you intend to use the same bytecode in the future. In case of any doubts regarding the implementation roadmap of the native precompile `P256` of potential future target chains, please consider using `verifySolidity`. +By default, the `verify` function will try calling the https://github.com/ethereum/RIPs/blob/master/RIPS/rip-7212.md[RIP-7212] precompile at address `0x100` and will fallback to an implementation in Solidity if not available. We encourage you to use `verifyNative` if you know the precompile is available on the chain you're working on and on any other chain on which you intend to use the same bytecode in the future. In case of any doubts regarding the implementation roadmap of the native precompile `P256` of potential future target chains, please consider using `verifySolidity`. [source,solidity] ---- @@ -73,7 +73,7 @@ IMPORTANT: The P256 library only allows for `s` values in the lower order of the ==== RSA -RSA a public-key cryptosystem that was popularized by corporate and governmental public key infrastructures (https://en.wikipedia.org/wiki/Public_key_infrastructure[PKIs]) and https://en.wikipedia.org/wiki/Domain_Name_System_Security_Extensions[DNSSEC]. +RSA is a public-key cryptosystem that was popularized by corporate and governmental public key infrastructures (https://en.wikipedia.org/wiki/Public_key_infrastructure[PKIs]) and https://en.wikipedia.org/wiki/Domain_Name_System_Security_Extensions[DNSSEC]. This cryptosystem consists of using a private key that's the product of 2 large prime numbers. The message is signed by applying a modular exponentiation to its hash (commonly SHA256), where both the exponent and modulus compose the public key of the signer. @@ -99,7 +99,7 @@ IMPORTANT: Always use keys of at least 2048 bits. Additionally, be aware that PK === Verifying Merkle Proofs -Developers can build a Merkle Tree off-chain, which allows for verifying that an element (leaf) is part of a set by using a Merkle Proof. This technique is widely used for creating whitelists (e.g. for airdrops) and other advanced use cases. +Developers can build a Merkle Tree off-chain, which allows for verifying that an element (leaf) is part of a set by using a Merkle Proof. This technique is widely used for creating whitelists (e.g., for airdrops) and other advanced use cases. TIP: OpenZeppelin Contracts provides a https://github.com/OpenZeppelin/merkle-tree[JavaScript library] for building trees off-chain and generating proofs. @@ -149,7 +149,7 @@ contract MyContract { [[math]] == Math -Although Solidity already provides math operators (i.e. `+`, `-`, etc.), Contracts includes xref:api:utils.adoc#Math[`Math`]; a set of utilities for dealing with mathematical operators, with support for extra operations (eg. xref:api:utils.adoc#Math-average-uint256-uint256-[`average`]) and xref:api:utils.adoc#SignedMath[`SignedMath`]; a library specialized in signed math operations. +Although Solidity already provides math operators (i.e. `+`, `-`, etc.), Contracts includes xref:api:utils.adoc#Math[`Math`]; a set of utilities for dealing with mathematical operators, with support for extra operations (e.g., xref:api:utils.adoc#Math-average-uint256-uint256-[`average`]) and xref:api:utils.adoc#SignedMath[`SignedMath`]; a library specialized in signed math operations. Include these contracts with `using Math for uint256` or `using SignedMath for int256` and then use their functions in your code: @@ -272,7 +272,7 @@ function replace(Uint256Heap storage self, uint256 newValue) internal returns (u === Packing -The storage in the EVM is shaped in chunks of 32 bytes, each of this chunks is known as _slot_, and can hold multiple values together as long as these values don't exceed its size. These properties of the storage allow for a technique known as _packing_, that consists of placing values together on a single storage slot to reduce the costs associated to reading and writing to multiple slots instead of just one. +The storage in the EVM is shaped in chunks of 32 bytes, each of this chunks is known as a _slot_, and can hold multiple values together as long as these values don't exceed its size. These properties of the storage allow for a technique known as _packing_, that consists of placing values together on a single storage slot to reduce the costs associated to reading and writing to multiple slots instead of just one. Commonly, developers pack values using structs that place values together so they fit better in storage. However, this approach requires to load such struct from either calldata or memory. Although sometimes necessary, it may be useful to pack values in a single slot and treat it as a packed value without involving calldata or memory. From e3cfe1c5ddbea74cca0494bb28361c8096e0160d Mon Sep 17 00:00:00 2001 From: cairo Date: Mon, 30 Sep 2024 09:05:44 -0700 Subject: [PATCH 21/84] Fix P256 corner cases (#5218) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Hadrien Croubois Co-authored-by: Ernesto García --- .solcover.js | 8 +++ contracts/utils/cryptography/P256.sol | 94 ++++++++++++++++++++------- test/helpers/iterate.js | 4 +- test/utils/cryptography/P256.t.sol | 12 ++-- 4 files changed, 88 insertions(+), 30 deletions(-) diff --git a/.solcover.js b/.solcover.js index e0dea5e2c9b..f079998cff3 100644 --- a/.solcover.js +++ b/.solcover.js @@ -10,4 +10,12 @@ module.exports = { fgrep: '[skip-on-coverage]', invert: true, }, + // Work around stack too deep for coverage + configureYulOptimizer: true, + solcOptimizerDetails: { + yul: true, + yulDetails: { + optimizerSteps: '', + }, + }, }; diff --git a/contracts/utils/cryptography/P256.sol b/contracts/utils/cryptography/P256.sol index 1c46e38b0be..3028505ba75 100644 --- a/contracts/utils/cryptography/P256.sol +++ b/contracts/utils/cryptography/P256.sol @@ -185,6 +185,13 @@ library P256 { /** * @dev Point addition on the jacobian coordinates * Reference: https://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian.html#addition-add-1998-cmo-2 + * + * Note that: + * + * - `addition-add-1998-cmo-2` doesn't support identical input points. This version is modified to use + * the `h` and `r` values computed by `addition-add-1998-cmo-2` to detect identical inputs, and fallback to + * `doubling-dbl-1998-cmo-2` if needed. + * - if one of the points is at infinity (i.e. `z=0`), the result is undefined. */ function _jAdd( JPoint memory p1, @@ -197,25 +204,53 @@ library P256 { let z1 := mload(add(p1, 0x40)) let zz1 := mulmod(z1, z1, p) // zz1 = z1² let s1 := mulmod(mload(add(p1, 0x20)), mulmod(mulmod(z2, z2, p), z2, p), p) // s1 = y1*z2³ - let r := addmod(mulmod(y2, mulmod(zz1, z1, p), p), sub(p, s1), p) // r = s2-s1 = y2*z1³-s1 + let r := addmod(mulmod(y2, mulmod(zz1, z1, p), p), sub(p, s1), p) // r = s2-s1 = y2*z1³-s1 = y2*z1³-y1*z2³ let u1 := mulmod(mload(p1), mulmod(z2, z2, p), p) // u1 = x1*z2² - let h := addmod(mulmod(x2, zz1, p), sub(p, u1), p) // h = u2-u1 = x2*z1²-u1 - let hh := mulmod(h, h, p) // h² + let h := addmod(mulmod(x2, zz1, p), sub(p, u1), p) // h = u2-u1 = x2*z1²-u1 = x2*z1²-x1*z2² + + // detect edge cases where inputs are identical + switch and(iszero(r), iszero(h)) + // case 0: points are different + case 0 { + let hh := mulmod(h, h, p) // h² + + // x' = r²-h³-2*u1*h² + rx := addmod( + addmod(mulmod(r, r, p), sub(p, mulmod(h, hh, p)), p), + sub(p, mulmod(2, mulmod(u1, hh, p), p)), + p + ) + // y' = r*(u1*h²-x')-s1*h³ + ry := addmod( + mulmod(r, addmod(mulmod(u1, hh, p), sub(p, rx), p), p), + sub(p, mulmod(s1, mulmod(h, hh, p), p)), + p + ) + // z' = h*z1*z2 + rz := mulmod(h, mulmod(z1, z2, p), p) + } + // case 1: points are equal + case 1 { + let x := x2 + let y := y2 + let z := z2 + let yy := mulmod(y, y, p) + let zz := mulmod(z, z, p) + let m := addmod(mulmod(3, mulmod(x, x, p), p), mulmod(A, mulmod(zz, zz, p), p), p) // m = 3*x²+a*z⁴ + let s := mulmod(4, mulmod(x, yy, p), p) // s = 4*x*y² + + // x' = t = m²-2*s + rx := addmod(mulmod(m, m, p), sub(p, mulmod(2, s, p)), p) - // x' = r²-h³-2*u1*h² - rx := addmod( - addmod(mulmod(r, r, p), sub(p, mulmod(h, hh, p)), p), - sub(p, mulmod(2, mulmod(u1, hh, p), p)), - p - ) - // y' = r*(u1*h²-x')-s1*h³ - ry := addmod( - mulmod(r, addmod(mulmod(u1, hh, p), sub(p, rx), p), p), - sub(p, mulmod(s1, mulmod(h, hh, p), p)), - p - ) - // z' = h*z1*z2 - rz := mulmod(h, mulmod(z1, z2, p), p) + // y' = m*(s-t)-8*y⁴ = m*(s-x')-8*y⁴ + // cut the computation to avoid stack too deep + let rytmp1 := sub(p, mulmod(8, mulmod(yy, yy, p), p)) // -8*y⁴ + let rytmp2 := addmod(s, sub(p, rx), p) // s-x' + ry := addmod(mulmod(m, rytmp2, p), rytmp1, p) // m*(s-x')-8*y⁴ + + // z' = 2*y*z + rz := mulmod(2, mulmod(y, z, p), p) + } } } @@ -228,8 +263,8 @@ library P256 { let p := P let yy := mulmod(y, y, p) let zz := mulmod(z, z, p) - let s := mulmod(4, mulmod(x, yy, p), p) // s = 4*x*y² let m := addmod(mulmod(3, mulmod(x, x, p), p), mulmod(A, mulmod(zz, zz, p), p), p) // m = 3*x²+a*z⁴ + let s := mulmod(4, mulmod(x, yy, p), p) // s = 4*x*y² // x' = t = m²-2*s rx := addmod(mulmod(m, m, p), sub(p, mulmod(2, s, p)), p) @@ -244,10 +279,11 @@ library P256 { * @dev Compute G·u1 + P·u2 using the precomputed points for G and P (see {_preComputeJacobianPoints}). * * Uses Strauss Shamir trick for EC multiplication - * https://stackoverflow.com/questions/50993471/ec-scalar-multiplication-with-strauss-shamir-method. - * We optimise on this a bit to do with 2 bits at a time rather than a single bit. - * The individual points for a single pass are precomputed. - * Overall this reduces the number of additions while keeping the same number of doublings. + * https://stackoverflow.com/questions/50993471/ec-scalar-multiplication-with-strauss-shamir-method + * + * We optimize this for 2 bits at a time rather than a single bit. The individual points for a single pass are + * precomputed. Overall this reduces the number of additions while keeping the same number of + * doublings */ function _jMultShamir( JPoint[16] memory points, @@ -263,9 +299,14 @@ library P256 { (x, y, z) = _jDouble(x, y, z); (x, y, z) = _jDouble(x, y, z); } - // Read 2 bits of u1, and 2 bits of u2. Combining the two give a lookup index in the table. + // Read 2 bits of u1, and 2 bits of u2. Combining the two gives the lookup index in the table. uint256 pos = ((u1 >> 252) & 0xc) | ((u2 >> 254) & 0x3); - if (pos > 0) { + // Points that have z = 0 are points at infinity. They are the additive 0 of the group + // - if the lookup point is a 0, we can skip it + // - otherwise: + // - if the current point (x, y, z) is 0, we use the lookup point as our new value (0+P=P) + // - if the current point (x, y, z) is not 0, both points are valid and we can use `_jAdd` + if (points[pos].z != 0) { if (z == 0) { (x, y, z) = (points[pos].x, points[pos].y, points[pos].z); } else { @@ -291,6 +332,11 @@ library P256 { * │ 8 │ 2g 2g+p 2g+2p 2g+3p │ * │ 12 │ 3g 3g+p 3g+2p 3g+3p │ * └────┴─────────────────────┘ + * + * Note that `_jAdd` (and thus `_jAddPoint`) does not handle the case where one of the inputs is a point at + * infinity (z = 0). However, we know that since `N ≡ 1 mod 2` and `N ≡ 1 mod 3`, there is no point P such that + * 2P = 0 or 3P = 0. This guarantees that g, 2g, 3g, p, 2p, 3p are all non-zero, and that all `_jAddPoint` calls + * have valid inputs. */ function _preComputeJacobianPoints(uint256 px, uint256 py) private pure returns (JPoint[16] memory points) { points[0x00] = JPoint(0, 0, 0); // 0,0 diff --git a/test/helpers/iterate.js b/test/helpers/iterate.js index ef4526e133f..c7403d52384 100644 --- a/test/helpers/iterate.js +++ b/test/helpers/iterate.js @@ -13,11 +13,11 @@ module.exports = { // Range from start to end in increment // Example: range(17,42,7) → [17,24,31,38] range: (start, stop = undefined, step = 1) => { - if (!stop) { + if (stop == undefined) { stop = start; start = 0; } - return start < stop ? Array.from({ length: Math.ceil((stop - start) / step) }, (_, i) => start + i * step) : []; + return start < stop ? Array.from({ length: (stop - start + step - 1) / step }, (_, i) => start + i * step) : []; }, // Unique elements, with an optional getter function diff --git a/test/utils/cryptography/P256.t.sol b/test/utils/cryptography/P256.t.sol index 1391afd76ef..8b95ff2259d 100644 --- a/test/utils/cryptography/P256.t.sol +++ b/test/utils/cryptography/P256.t.sol @@ -9,8 +9,8 @@ import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; contract P256Test is Test { /// forge-config: default.fuzz.runs = 512 - function testVerify(uint256 seed, bytes32 digest) public { - uint256 privateKey = bound(uint256(keccak256(abi.encode(seed))), 1, P256.N - 1); + function testVerify(bytes32 digest, uint256 seed) public { + uint256 privateKey = _asPrivateKey(seed); (bytes32 x, bytes32 y) = P256PublicKey.getPublicKey(privateKey); (bytes32 r, bytes32 s) = vm.signP256(privateKey, digest); @@ -20,8 +20,8 @@ contract P256Test is Test { } /// forge-config: default.fuzz.runs = 512 - function testRecover(uint256 seed, bytes32 digest) public { - uint256 privateKey = bound(uint256(keccak256(abi.encode(seed))), 1, P256.N - 1); + function testRecover(bytes32 digest, uint256 seed) public { + uint256 privateKey = _asPrivateKey(seed); (bytes32 x, bytes32 y) = P256PublicKey.getPublicKey(privateKey); (bytes32 r, bytes32 s) = vm.signP256(privateKey, digest); @@ -31,6 +31,10 @@ contract P256Test is Test { assertTrue((qx0 == x && qy0 == y) || (qx1 == x && qy1 == y)); } + function _asPrivateKey(uint256 seed) private pure returns (uint256) { + return bound(seed, 1, P256.N - 1); + } + function _ensureLowerS(bytes32 s) private pure returns (bytes32) { uint256 _s = uint256(s); unchecked { From b72e3da0ec1f47e4a7911a4c06dc92e78c646607 Mon Sep 17 00:00:00 2001 From: cairo Date: Mon, 30 Sep 2024 14:38:42 -0700 Subject: [PATCH 22/84] Bump forge-std to v1.9.3 (#5230) --- lib/forge-std | 2 +- test/utils/cryptography/P256.t.sol | 105 ++--------------------------- 2 files changed, 6 insertions(+), 101 deletions(-) diff --git a/lib/forge-std b/lib/forge-std index ae570fec082..8f24d6b04c9 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit ae570fec082bfe1c1f45b0acca4a2b4f84d345ce +Subproject commit 8f24d6b04c92975e0795b5868aa0d783251cdeaa diff --git a/test/utils/cryptography/P256.t.sol b/test/utils/cryptography/P256.t.sol index 8b95ff2259d..0c9b2c78a04 100644 --- a/test/utils/cryptography/P256.t.sol +++ b/test/utils/cryptography/P256.t.sol @@ -12,23 +12,23 @@ contract P256Test is Test { function testVerify(bytes32 digest, uint256 seed) public { uint256 privateKey = _asPrivateKey(seed); - (bytes32 x, bytes32 y) = P256PublicKey.getPublicKey(privateKey); + (uint256 x, uint256 y) = vm.publicKeyP256(privateKey); (bytes32 r, bytes32 s) = vm.signP256(privateKey, digest); s = _ensureLowerS(s); - assertTrue(P256.verify(digest, r, s, x, y)); - assertTrue(P256.verifySolidity(digest, r, s, x, y)); + assertTrue(P256.verify(digest, r, s, bytes32(x), bytes32(y))); + assertTrue(P256.verifySolidity(digest, r, s, bytes32(x), bytes32(y))); } /// forge-config: default.fuzz.runs = 512 function testRecover(bytes32 digest, uint256 seed) public { uint256 privateKey = _asPrivateKey(seed); - (bytes32 x, bytes32 y) = P256PublicKey.getPublicKey(privateKey); + (uint256 x, uint256 y) = vm.publicKeyP256(privateKey); (bytes32 r, bytes32 s) = vm.signP256(privateKey, digest); s = _ensureLowerS(s); (bytes32 qx0, bytes32 qy0) = P256.recovery(digest, 0, r, s); (bytes32 qx1, bytes32 qy1) = P256.recovery(digest, 1, r, s); - assertTrue((qx0 == x && qy0 == y) || (qx1 == x && qy1 == y)); + assertTrue((qx0 == bytes32(x) && qy0 == bytes32(y)) || (qx1 == bytes32(x) && qy1 == bytes32(y))); } function _asPrivateKey(uint256 seed) private pure returns (uint256) { @@ -42,98 +42,3 @@ contract P256Test is Test { } } } - -/** - * @dev Library to derive P256 public key from private key - * Should be removed if Foundry adds this functionality - * See https://github.com/foundry-rs/foundry/issues/7908 - */ -library P256PublicKey { - function getPublicKey(uint256 privateKey) internal view returns (bytes32, bytes32) { - (uint256 x, uint256 y, uint256 z) = _jMult(P256.GX, P256.GY, 1, privateKey); - return _affineFromJacobian(x, y, z); - } - - function _jMult( - uint256 x, - uint256 y, - uint256 z, - uint256 k - ) private pure returns (uint256 rx, uint256 ry, uint256 rz) { - unchecked { - for (uint256 i = 0; i < 256; ++i) { - if (rz > 0) { - (rx, ry, rz) = _jDouble(rx, ry, rz); - } - if (k >> 255 > 0) { - if (rz == 0) { - (rx, ry, rz) = (x, y, z); - } else { - (rx, ry, rz) = _jAdd(rx, ry, rz, x, y, z); - } - } - k <<= 1; - } - } - } - - /// From P256.sol - - function _affineFromJacobian(uint256 jx, uint256 jy, uint256 jz) private view returns (bytes32 ax, bytes32 ay) { - if (jz == 0) return (0, 0); - uint256 zinv = Math.invModPrime(jz, P256.P); - uint256 zzinv = mulmod(zinv, zinv, P256.P); - uint256 zzzinv = mulmod(zzinv, zinv, P256.P); - ax = bytes32(mulmod(jx, zzinv, P256.P)); - ay = bytes32(mulmod(jy, zzzinv, P256.P)); - } - - function _jDouble(uint256 x, uint256 y, uint256 z) private pure returns (uint256 rx, uint256 ry, uint256 rz) { - uint256 p = P256.P; - uint256 a = P256.A; - assembly ("memory-safe") { - let yy := mulmod(y, y, p) - let zz := mulmod(z, z, p) - let s := mulmod(4, mulmod(x, yy, p), p) // s = 4*x*y² - let m := addmod(mulmod(3, mulmod(x, x, p), p), mulmod(a, mulmod(zz, zz, p), p), p) // m = 3*x²+a*z⁴ - let t := addmod(mulmod(m, m, p), sub(p, mulmod(2, s, p)), p) // t = m²-2*s - - // x' = t - rx := t - // y' = m*(s-t)-8*y⁴ - ry := addmod(mulmod(m, addmod(s, sub(p, t), p), p), sub(p, mulmod(8, mulmod(yy, yy, p), p)), p) - // z' = 2*y*z - rz := mulmod(2, mulmod(y, z, p), p) - } - } - - function _jAdd( - uint256 x1, - uint256 y1, - uint256 z1, - uint256 x2, - uint256 y2, - uint256 z2 - ) private pure returns (uint256 rx, uint256 ry, uint256 rz) { - uint256 p = P256.P; - assembly ("memory-safe") { - let zz1 := mulmod(z1, z1, p) // zz1 = z1² - let zz2 := mulmod(z2, z2, p) // zz2 = z2² - let u1 := mulmod(x1, zz2, p) // u1 = x1*z2² - let u2 := mulmod(x2, zz1, p) // u2 = x2*z1² - let s1 := mulmod(y1, mulmod(zz2, z2, p), p) // s1 = y1*z2³ - let s2 := mulmod(y2, mulmod(zz1, z1, p), p) // s2 = y2*z1³ - let h := addmod(u2, sub(p, u1), p) // h = u2-u1 - let hh := mulmod(h, h, p) // h² - let hhh := mulmod(h, hh, p) // h³ - let r := addmod(s2, sub(p, s1), p) // r = s2-s1 - - // x' = r²-h³-2*u1*h² - rx := addmod(addmod(mulmod(r, r, p), sub(p, hhh), p), sub(p, mulmod(2, mulmod(u1, hh, p), p)), p) - // y' = r*(u1*h²-x')-s1*h³ - ry := addmod(mulmod(r, addmod(mulmod(u1, hh, p), sub(p, rx), p), p), sub(p, mulmod(s1, hhh, p)), p) - // z' = h*z1*z2 - rz := mulmod(h, mulmod(z1, z2, p), p) - } - } -} From 49cd64565aafa5b8f6863bf60a30ef015861614c Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Tue, 1 Oct 2024 19:50:58 +0200 Subject: [PATCH 23/84] Add warning about low public key exponent (#5234) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernesto García --- contracts/utils/cryptography/RSA.sol | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/contracts/utils/cryptography/RSA.sol b/contracts/utils/cryptography/RSA.sol index c8da4f96206..70c38fd15bb 100644 --- a/contracts/utils/cryptography/RSA.sol +++ b/contracts/utils/cryptography/RSA.sol @@ -36,8 +36,12 @@ library RSA { * 2048 bits. If you use a smaller key, consider replacing it with a larger, more secure, one. * * WARNING: This verification algorithm doesn't prevent replayability. If called multiple times with the same - * digest, public key and (valid signature), it will return true every time. Consider including an onchain nonce or - * unique identifier in the message to prevent replay attacks. + * digest, public key and (valid signature), it will return true every time. Consider including an onchain nonce + * or unique identifier in the message to prevent replay attacks. + * + * WARNING: This verification algorithm supports any exponent. NIST recommends using `65537` (or higher). + * That is the default value many libraries use, such as OpenSSL. Developers may choose to reject public keys + * using a low exponent out of security concerns. * * @param digest the digest to verify * @param s is a buffer containing the signature From 8b591baef460523e5ca1c53712c464bcc1a1c467 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Thu, 3 Oct 2024 13:24:43 -0600 Subject: [PATCH 24/84] Add 5.1 audit report (#5237) --- audits/2024-10-v5.1.pdf | Bin 0 -> 395831 bytes audits/README.md | 1 + 2 files changed, 1 insertion(+) create mode 100644 audits/2024-10-v5.1.pdf diff --git a/audits/2024-10-v5.1.pdf b/audits/2024-10-v5.1.pdf new file mode 100644 index 0000000000000000000000000000000000000000..4d85dc7ba9ea633910f31cbf526611ab626f322a GIT binary patch literal 395831 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
>u|qF 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�R0pl!Gh|V83jf>^ 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

Sve@6z;dti&qM@hjdwzFR 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 Date: Wed, 9 Oct 2024 00:41:30 +0530 Subject: [PATCH 25/84] Fix typo in CHANGELOG.md (#5240) --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f327a1cb446..b64fabab220 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ### Breaking changes - `ERC1967Utils`: Removed duplicate declaration of the `Upgraded`, `AdminChanged` and `BeaconUpgraded` events. These events are still available through the `IERC1967` interface located under the `contracts/interfaces/` directory. Minimum pragma version is now 0.8.21. -- `Governor`, `GovernorCountingSimple`: The `_countVotes` virtual function now returns an `uint256` with the total votes casted. This change allows for more flexibility for partial and fractional voting. Upgrading users may get a compilation error that can be fixed by adding a return statement to the `_countVotes` function. +- `Governor`, `GovernorCountingSimple`: The `_countVote` virtual function now returns an `uint256` with the total votes casted. This change allows for more flexibility for partial and fractional voting. Upgrading users may get a compilation error that can be fixed by adding a return statement to the `_countVote` function. ### Custom error changes From 632500967504310a07f9d2c70ad378cf53be0109 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Tue, 8 Oct 2024 13:39:53 -0600 Subject: [PATCH 26/84] Split StorageSlot into TransientSlot (#5239) Co-authored-by: Hadrien Croubois --- contracts/mocks/StorageSlotMock.sol | 52 +---- contracts/mocks/TransientSlotMock.sol | 61 ++++++ .../draft-ERC20TemporaryApproval.sol | 11 +- contracts/utils/README.adoc | 5 +- contracts/utils/ReentrancyGuardTransient.sol | 4 +- contracts/utils/StorageSlot.sol | 170 +--------------- contracts/utils/TransientSlot.sol | 182 ++++++++++++++++++ docs/modules/ROOT/pages/utilities.adoc | 2 +- scripts/generate/run.js | 2 + scripts/generate/templates/StorageSlot.js | 56 +----- scripts/generate/templates/StorageSlotMock.js | 15 +- scripts/generate/templates/TransientSlot.js | 80 ++++++++ .../generate/templates/TransientSlotMock.js | 35 ++++ test/utils/StorageSlot.test.js | 33 ---- test/utils/TransientSlot.test.js | 59 ++++++ 15 files changed, 434 insertions(+), 333 deletions(-) create mode 100644 contracts/mocks/TransientSlotMock.sol create mode 100644 contracts/utils/TransientSlot.sol create mode 100644 scripts/generate/templates/TransientSlot.js create mode 100644 scripts/generate/templates/TransientSlotMock.js create mode 100644 test/utils/TransientSlot.test.js diff --git a/contracts/mocks/StorageSlotMock.sol b/contracts/mocks/StorageSlotMock.sol index e69bd36a249..ec176e21fb5 100644 --- a/contracts/mocks/StorageSlotMock.sol +++ b/contracts/mocks/StorageSlotMock.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // This file was procedurally generated from scripts/generate/templates/StorageSlotMock.js. -pragma solidity ^0.8.24; +pragma solidity ^0.8.20; import {Multicall} from "../utils/Multicall.sol"; import {StorageSlot} from "../utils/StorageSlot.sol"; @@ -84,54 +84,4 @@ contract StorageSlotMock is Multicall { function getBytesStorage(uint256 key) public view returns (bytes memory) { return bytesMap[key].getBytesSlot().value; } - - event AddressValue(bytes32 slot, address value); - - function tloadAddress(bytes32 slot) public { - emit AddressValue(slot, slot.asAddress().tload()); - } - - function tstore(bytes32 slot, address value) public { - slot.asAddress().tstore(value); - } - - event BooleanValue(bytes32 slot, bool value); - - function tloadBoolean(bytes32 slot) public { - emit BooleanValue(slot, slot.asBoolean().tload()); - } - - function tstore(bytes32 slot, bool value) public { - slot.asBoolean().tstore(value); - } - - event Bytes32Value(bytes32 slot, bytes32 value); - - function tloadBytes32(bytes32 slot) public { - emit Bytes32Value(slot, slot.asBytes32().tload()); - } - - function tstore(bytes32 slot, bytes32 value) public { - slot.asBytes32().tstore(value); - } - - event Uint256Value(bytes32 slot, uint256 value); - - function tloadUint256(bytes32 slot) public { - emit Uint256Value(slot, slot.asUint256().tload()); - } - - function tstore(bytes32 slot, uint256 value) public { - slot.asUint256().tstore(value); - } - - event Int256Value(bytes32 slot, int256 value); - - function tloadInt256(bytes32 slot) public { - emit Int256Value(slot, slot.asInt256().tload()); - } - - function tstore(bytes32 slot, int256 value) public { - slot.asInt256().tstore(value); - } } diff --git a/contracts/mocks/TransientSlotMock.sol b/contracts/mocks/TransientSlotMock.sol new file mode 100644 index 00000000000..6b18fa52f29 --- /dev/null +++ b/contracts/mocks/TransientSlotMock.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: MIT +// This file was procedurally generated from scripts/generate/templates/TransientSlotMock.js. + +pragma solidity ^0.8.24; + +import {Multicall} from "../utils/Multicall.sol"; +import {TransientSlot} from "../utils/TransientSlot.sol"; + +contract TransientSlotMock is Multicall { + using TransientSlot for *; + + event AddressValue(bytes32 slot, address value); + + function tloadAddress(bytes32 slot) public { + emit AddressValue(slot, slot.asAddress().tload()); + } + + function tstore(bytes32 slot, address value) public { + slot.asAddress().tstore(value); + } + + event BooleanValue(bytes32 slot, bool value); + + function tloadBoolean(bytes32 slot) public { + emit BooleanValue(slot, slot.asBoolean().tload()); + } + + function tstore(bytes32 slot, bool value) public { + slot.asBoolean().tstore(value); + } + + event Bytes32Value(bytes32 slot, bytes32 value); + + function tloadBytes32(bytes32 slot) public { + emit Bytes32Value(slot, slot.asBytes32().tload()); + } + + function tstore(bytes32 slot, bytes32 value) public { + slot.asBytes32().tstore(value); + } + + event Uint256Value(bytes32 slot, uint256 value); + + function tloadUint256(bytes32 slot) public { + emit Uint256Value(slot, slot.asUint256().tload()); + } + + function tstore(bytes32 slot, uint256 value) public { + slot.asUint256().tstore(value); + } + + event Int256Value(bytes32 slot, int256 value); + + function tloadInt256(bytes32 slot) public { + emit Int256Value(slot, slot.asInt256().tload()); + } + + function tstore(bytes32 slot, int256 value) public { + slot.asInt256().tstore(value); + } +} diff --git a/contracts/token/ERC20/extensions/draft-ERC20TemporaryApproval.sol b/contracts/token/ERC20/extensions/draft-ERC20TemporaryApproval.sol index 515b080ea16..c5a29685c7f 100644 --- a/contracts/token/ERC20/extensions/draft-ERC20TemporaryApproval.sol +++ b/contracts/token/ERC20/extensions/draft-ERC20TemporaryApproval.sol @@ -6,7 +6,7 @@ import {IERC20, ERC20} from "../ERC20.sol"; import {IERC7674} from "../../../interfaces/draft-IERC7674.sol"; import {Math} from "../../../utils/math/Math.sol"; import {SlotDerivation} from "../../../utils/SlotDerivation.sol"; -import {StorageSlot} from "../../../utils/StorageSlot.sol"; +import {TransientSlot} from "../../../utils/TransientSlot.sol"; /** * @dev Extension of {ERC20} that adds support for temporary allowances following ERC-7674. @@ -17,8 +17,8 @@ import {StorageSlot} from "../../../utils/StorageSlot.sol"; */ abstract contract ERC20TemporaryApproval is ERC20, IERC7674 { using SlotDerivation for bytes32; - using StorageSlot for bytes32; - using StorageSlot for StorageSlot.Uint256SlotType; + using TransientSlot for bytes32; + using TransientSlot for TransientSlot.Uint256Slot; // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ERC20_TEMPORARY_APPROVAL_STORAGE")) - 1)) & ~bytes32(uint256(0xff)) bytes32 private constant ERC20_TEMPORARY_APPROVAL_STORAGE = @@ -112,10 +112,7 @@ abstract contract ERC20TemporaryApproval is ERC20, IERC7674 { } } - function _temporaryAllowanceSlot( - address owner, - address spender - ) private pure returns (StorageSlot.Uint256SlotType) { + function _temporaryAllowanceSlot(address owner, address spender) private pure returns (TransientSlot.Uint256Slot) { return ERC20_TEMPORARY_APPROVAL_STORAGE.deriveMapping(owner).deriveMapping(spender).asUint256(); } } diff --git a/contracts/utils/README.adoc b/contracts/utils/README.adoc index 4b40a967efd..24b95b4e6f8 100644 --- a/contracts/utils/README.adoc +++ b/contracts/utils/README.adoc @@ -34,7 +34,8 @@ Miscellaneous contracts and libraries containing utility functions you can use t * {Strings}: Common operations for strings formatting. * {ShortString}: Library to encode (and decode) short strings into (or from) a single bytes32 slot for optimizing costs. Short strings are limited to 31 characters. * {SlotDerivation}: Methods for deriving storage slot from ERC-7201 namespaces as well as from constructions such as mapping and arrays. - * {StorageSlot}: Methods for accessing specific storage slots formatted as common primitive types. Also include primitives for reading from and writing to transient storage (only value types are currently supported). + * {StorageSlot}: Methods for accessing specific storage slots formatted as common primitive types. + * {TransientSlot}: Primitives for reading from and writing to transient storage (only value types are currently supported). * {Multicall}: Abstract contract with a utility to allow batching together multiple calls in a single transaction. Useful for allowing EOAs to perform multiple operations at once. * {Context}: A utility for abstracting the sender and calldata in the current execution context. * {Packing}: A library for packing and unpacking multiple values into bytes32 @@ -130,6 +131,8 @@ Ethereum contracts have no native concept of an interface, so applications must {{StorageSlot}} +{{TransientSlot}} + {{Multicall}} {{Context}} diff --git a/contracts/utils/ReentrancyGuardTransient.sol b/contracts/utils/ReentrancyGuardTransient.sol index 54df0a71ac8..ba8cbb65b1f 100644 --- a/contracts/utils/ReentrancyGuardTransient.sol +++ b/contracts/utils/ReentrancyGuardTransient.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.24; -import {StorageSlot} from "./StorageSlot.sol"; +import {TransientSlot} from "./TransientSlot.sol"; /** * @dev Variant of {ReentrancyGuard} that uses transient storage. @@ -12,7 +12,7 @@ import {StorageSlot} from "./StorageSlot.sol"; * _Available since v5.1._ */ abstract contract ReentrancyGuardTransient { - using StorageSlot for *; + using TransientSlot for *; // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ReentrancyGuard")) - 1)) & ~bytes32(uint256(0xff)) bytes32 private constant REENTRANCY_GUARD_STORAGE = diff --git a/contracts/utils/StorageSlot.sol b/contracts/utils/StorageSlot.sol index 2e4f736d8d1..2df32cb9278 100644 --- a/contracts/utils/StorageSlot.sol +++ b/contracts/utils/StorageSlot.sol @@ -2,7 +2,7 @@ // OpenZeppelin Contracts (last updated v5.0.0) (utils/StorageSlot.sol) // This file was procedurally generated from scripts/generate/templates/StorageSlot.js. -pragma solidity ^0.8.24; +pragma solidity ^0.8.20; /** * @dev Library for reading and writing primitive types to specific storage slots. @@ -29,24 +29,6 @@ pragma solidity ^0.8.24; * } * ``` * - * Since version 5.1, this library also support writing and reading value types to and from transient storage. - * - * * Example using transient storage: - * ```solidity - * contract Lock { - * // Define the slot. Alternatively, use the SlotDerivation library to derive the slot. - * bytes32 internal constant _LOCK_SLOT = 0xf4678858b2b588224636b8522b729e7722d32fc491da849ed75b3fdf3c84f542; - * - * modifier locked() { - * require(!_LOCK_SLOT.asBoolean().tload()); - * - * _LOCK_SLOT.asBoolean().tstore(true); - * _; - * _LOCK_SLOT.asBoolean().tstore(false); - * } - * } - * ``` - * * TIP: Consider using this library along with {SlotDerivation}. */ library StorageSlot { @@ -158,154 +140,4 @@ library StorageSlot { r.slot := store.slot } } - - /** - * @dev UDVT that represent a slot holding a address. - */ - type AddressSlotType is bytes32; - - /** - * @dev Cast an arbitrary slot to a AddressSlotType. - */ - function asAddress(bytes32 slot) internal pure returns (AddressSlotType) { - return AddressSlotType.wrap(slot); - } - - /** - * @dev UDVT that represent a slot holding a bool. - */ - type BooleanSlotType is bytes32; - - /** - * @dev Cast an arbitrary slot to a BooleanSlotType. - */ - function asBoolean(bytes32 slot) internal pure returns (BooleanSlotType) { - return BooleanSlotType.wrap(slot); - } - - /** - * @dev UDVT that represent a slot holding a bytes32. - */ - type Bytes32SlotType is bytes32; - - /** - * @dev Cast an arbitrary slot to a Bytes32SlotType. - */ - function asBytes32(bytes32 slot) internal pure returns (Bytes32SlotType) { - return Bytes32SlotType.wrap(slot); - } - - /** - * @dev UDVT that represent a slot holding a uint256. - */ - type Uint256SlotType is bytes32; - - /** - * @dev Cast an arbitrary slot to a Uint256SlotType. - */ - function asUint256(bytes32 slot) internal pure returns (Uint256SlotType) { - return Uint256SlotType.wrap(slot); - } - - /** - * @dev UDVT that represent a slot holding a int256. - */ - type Int256SlotType is bytes32; - - /** - * @dev Cast an arbitrary slot to a Int256SlotType. - */ - function asInt256(bytes32 slot) internal pure returns (Int256SlotType) { - return Int256SlotType.wrap(slot); - } - - /** - * @dev Load the value held at location `slot` in transient storage. - */ - function tload(AddressSlotType slot) internal view returns (address value) { - assembly ("memory-safe") { - value := tload(slot) - } - } - - /** - * @dev Store `value` at location `slot` in transient storage. - */ - function tstore(AddressSlotType slot, address value) internal { - assembly ("memory-safe") { - tstore(slot, value) - } - } - - /** - * @dev Load the value held at location `slot` in transient storage. - */ - function tload(BooleanSlotType slot) internal view returns (bool value) { - assembly ("memory-safe") { - value := tload(slot) - } - } - - /** - * @dev Store `value` at location `slot` in transient storage. - */ - function tstore(BooleanSlotType slot, bool value) internal { - assembly ("memory-safe") { - tstore(slot, value) - } - } - - /** - * @dev Load the value held at location `slot` in transient storage. - */ - function tload(Bytes32SlotType slot) internal view returns (bytes32 value) { - assembly ("memory-safe") { - value := tload(slot) - } - } - - /** - * @dev Store `value` at location `slot` in transient storage. - */ - function tstore(Bytes32SlotType slot, bytes32 value) internal { - assembly ("memory-safe") { - tstore(slot, value) - } - } - - /** - * @dev Load the value held at location `slot` in transient storage. - */ - function tload(Uint256SlotType slot) internal view returns (uint256 value) { - assembly ("memory-safe") { - value := tload(slot) - } - } - - /** - * @dev Store `value` at location `slot` in transient storage. - */ - function tstore(Uint256SlotType slot, uint256 value) internal { - assembly ("memory-safe") { - tstore(slot, value) - } - } - - /** - * @dev Load the value held at location `slot` in transient storage. - */ - function tload(Int256SlotType slot) internal view returns (int256 value) { - assembly ("memory-safe") { - value := tload(slot) - } - } - - /** - * @dev Store `value` at location `slot` in transient storage. - */ - function tstore(Int256SlotType slot, int256 value) internal { - assembly ("memory-safe") { - tstore(slot, value) - } - } } diff --git a/contracts/utils/TransientSlot.sol b/contracts/utils/TransientSlot.sol new file mode 100644 index 00000000000..f1f67bdceb0 --- /dev/null +++ b/contracts/utils/TransientSlot.sol @@ -0,0 +1,182 @@ +// SPDX-License-Identifier: MIT +// This file was procedurally generated from scripts/generate/templates/TransientSlot.js. + +pragma solidity ^0.8.24; + +/** + * @dev Library for reading and writing value-types to specific transient storage slots. + * + * Transient slots are often used to store temporary values that are removed after the current transaction. + * This library helps with reading and writing to such slots without the need for inline assembly. + * + * * Example reading and writing values using transient storage: + * ```solidity + * contract Lock { + * using TransientSlot for *; + * + * // Define the slot. Alternatively, use the SlotDerivation library to derive the slot. + * bytes32 internal constant _LOCK_SLOT = 0xf4678858b2b588224636b8522b729e7722d32fc491da849ed75b3fdf3c84f542; + * + * modifier locked() { + * require(!_LOCK_SLOT.asBoolean().tload()); + * + * _LOCK_SLOT.asBoolean().tstore(true); + * _; + * _LOCK_SLOT.asBoolean().tstore(false); + * } + * } + * ``` + * + * TIP: Consider using this library along with {SlotDerivation}. + */ +library TransientSlot { + /** + * @dev UDVT that represent a slot holding a address. + */ + type AddressSlot is bytes32; + + /** + * @dev Cast an arbitrary slot to a AddressSlot. + */ + function asAddress(bytes32 slot) internal pure returns (AddressSlot) { + return AddressSlot.wrap(slot); + } + + /** + * @dev UDVT that represent a slot holding a bool. + */ + type BooleanSlot is bytes32; + + /** + * @dev Cast an arbitrary slot to a BooleanSlot. + */ + function asBoolean(bytes32 slot) internal pure returns (BooleanSlot) { + return BooleanSlot.wrap(slot); + } + + /** + * @dev UDVT that represent a slot holding a bytes32. + */ + type Bytes32Slot is bytes32; + + /** + * @dev Cast an arbitrary slot to a Bytes32Slot. + */ + function asBytes32(bytes32 slot) internal pure returns (Bytes32Slot) { + return Bytes32Slot.wrap(slot); + } + + /** + * @dev UDVT that represent a slot holding a uint256. + */ + type Uint256Slot is bytes32; + + /** + * @dev Cast an arbitrary slot to a Uint256Slot. + */ + function asUint256(bytes32 slot) internal pure returns (Uint256Slot) { + return Uint256Slot.wrap(slot); + } + + /** + * @dev UDVT that represent a slot holding a int256. + */ + type Int256Slot is bytes32; + + /** + * @dev Cast an arbitrary slot to a Int256Slot. + */ + function asInt256(bytes32 slot) internal pure returns (Int256Slot) { + return Int256Slot.wrap(slot); + } + + /** + * @dev Load the value held at location `slot` in transient storage. + */ + function tload(AddressSlot slot) internal view returns (address value) { + assembly ("memory-safe") { + value := tload(slot) + } + } + + /** + * @dev Store `value` at location `slot` in transient storage. + */ + function tstore(AddressSlot slot, address value) internal { + assembly ("memory-safe") { + tstore(slot, value) + } + } + + /** + * @dev Load the value held at location `slot` in transient storage. + */ + function tload(BooleanSlot slot) internal view returns (bool value) { + assembly ("memory-safe") { + value := tload(slot) + } + } + + /** + * @dev Store `value` at location `slot` in transient storage. + */ + function tstore(BooleanSlot slot, bool value) internal { + assembly ("memory-safe") { + tstore(slot, value) + } + } + + /** + * @dev Load the value held at location `slot` in transient storage. + */ + function tload(Bytes32Slot slot) internal view returns (bytes32 value) { + assembly ("memory-safe") { + value := tload(slot) + } + } + + /** + * @dev Store `value` at location `slot` in transient storage. + */ + function tstore(Bytes32Slot slot, bytes32 value) internal { + assembly ("memory-safe") { + tstore(slot, value) + } + } + + /** + * @dev Load the value held at location `slot` in transient storage. + */ + function tload(Uint256Slot slot) internal view returns (uint256 value) { + assembly ("memory-safe") { + value := tload(slot) + } + } + + /** + * @dev Store `value` at location `slot` in transient storage. + */ + function tstore(Uint256Slot slot, uint256 value) internal { + assembly ("memory-safe") { + tstore(slot, value) + } + } + + /** + * @dev Load the value held at location `slot` in transient storage. + */ + function tload(Int256Slot slot) internal view returns (int256 value) { + assembly ("memory-safe") { + value := tload(slot) + } + } + + /** + * @dev Store `value` at location `slot` in transient storage. + */ + function tstore(Int256Slot slot, int256 value) internal { + assembly ("memory-safe") { + tstore(slot, value) + } + } +} diff --git a/docs/modules/ROOT/pages/utilities.adoc b/docs/modules/ROOT/pages/utilities.adoc index adee6477f95..bb519907d7a 100644 --- a/docs/modules/ROOT/pages/utilities.adoc +++ b/docs/modules/ROOT/pages/utilities.adoc @@ -319,7 +319,7 @@ function _setImplementation(address newImplementation) internal { } ---- -The xref:api:utils.adoc#StorageSlot[`StorageSlot`] library also supports transient storage through user defined value types (https://docs.soliditylang.org/en/latest/types.html#user-defined-value-types[UDVTs]), which enables the same value types as in Solidity. +The xref:api:utils.adoc#TransientSlot[`TransientSlot`] library supports transient storage through user defined value types (https://docs.soliditylang.org/en/latest/types.html#user-defined-value-types[UDVTs]), which enables the same value types as in Solidity. [source,solidity] ---- diff --git a/scripts/generate/run.js b/scripts/generate/run.js index c28d1175b12..e4947eb12bd 100755 --- a/scripts/generate/run.js +++ b/scripts/generate/run.js @@ -39,9 +39,11 @@ for (const [file, template] of Object.entries({ 'utils/structs/EnumerableMap.sol': './templates/EnumerableMap.js', 'utils/SlotDerivation.sol': './templates/SlotDerivation.js', 'utils/StorageSlot.sol': './templates/StorageSlot.js', + 'utils/TransientSlot.sol': './templates/TransientSlot.js', 'utils/Arrays.sol': './templates/Arrays.js', 'utils/Packing.sol': './templates/Packing.js', 'mocks/StorageSlotMock.sol': './templates/StorageSlotMock.js', + 'mocks/TransientSlotMock.sol': './templates/TransientSlotMock.js', })) { generateFromTemplate(file, template, './contracts/'); } diff --git a/scripts/generate/templates/StorageSlot.js b/scripts/generate/templates/StorageSlot.js index 7a00f5e225c..53287b81fd9 100644 --- a/scripts/generate/templates/StorageSlot.js +++ b/scripts/generate/templates/StorageSlot.js @@ -2,7 +2,7 @@ const format = require('../format-lines'); const { TYPES } = require('./Slot.opts'); const header = `\ -pragma solidity ^0.8.24; +pragma solidity ^0.8.20; /** * @dev Library for reading and writing primitive types to specific storage slots. @@ -29,24 +29,6 @@ pragma solidity ^0.8.24; * } * \`\`\` * - * Since version 5.1, this library also support writing and reading value types to and from transient storage. - * - * * Example using transient storage: - * \`\`\`solidity - * contract Lock { - * // Define the slot. Alternatively, use the SlotDerivation library to derive the slot. - * bytes32 internal constant _LOCK_SLOT = 0xf4678858b2b588224636b8522b729e7722d32fc491da849ed75b3fdf3c84f542; - * - * modifier locked() { - * require(!_LOCK_SLOT.asBoolean().tload()); - * - * _LOCK_SLOT.asBoolean().tstore(true); - * _; - * _LOCK_SLOT.asBoolean().tstore(false); - * } - * } - * \`\`\` - * * TIP: Consider using this library along with {SlotDerivation}. */ `; @@ -81,40 +63,6 @@ function get${name}Slot(${type} storage store) internal pure returns (${name}Slo } `; -const udvt = ({ type, name }) => `\ -/** - * @dev UDVT that represent a slot holding a ${type}. - */ -type ${name}SlotType is bytes32; - -/** - * @dev Cast an arbitrary slot to a ${name}SlotType. - */ -function as${name}(bytes32 slot) internal pure returns (${name}SlotType) { - return ${name}SlotType.wrap(slot); -} -`; - -const transient = ({ type, name }) => `\ -/** - * @dev Load the value held at location \`slot\` in transient storage. - */ -function tload(${name}SlotType slot) internal view returns (${type} value) { - assembly ("memory-safe") { - value := tload(slot) - } -} - -/** - * @dev Store \`value\` at location \`slot\` in transient storage. - */ -function tstore(${name}SlotType slot, ${type} value) internal { - assembly ("memory-safe") { - tstore(slot, value) - } -} -`; - // GENERATE module.exports = format( header.trimEnd(), @@ -123,8 +71,6 @@ module.exports = format( [].concat( TYPES.map(type => struct(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 => transient(type)), ), ).trimEnd(), '}', diff --git a/scripts/generate/templates/StorageSlotMock.js b/scripts/generate/templates/StorageSlotMock.js index 623a6759247..c6d326a5e26 100644 --- a/scripts/generate/templates/StorageSlotMock.js +++ b/scripts/generate/templates/StorageSlotMock.js @@ -2,7 +2,7 @@ const format = require('../format-lines'); const { TYPES } = require('./Slot.opts'); const header = `\ -pragma solidity ^0.8.24; +pragma solidity ^0.8.20; import {Multicall} from "../utils/Multicall.sol"; import {StorageSlot} from "../utils/StorageSlot.sol"; @@ -40,18 +40,6 @@ function get${name}Storage(uint256 key) public view returns (${type} memory) { } `; -const transient = ({ type, name }) => `\ -event ${name}Value(bytes32 slot, ${type} value); - -function tload${name}(bytes32 slot) public { - emit ${name}Value(slot, slot.as${name}().tload()); -} - -function tstore(bytes32 slot, ${type} value) public { - slot.as${name}().tstore(value); -} -`; - // GENERATE module.exports = format( header, @@ -63,7 +51,6 @@ module.exports = format( 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 => storageSetNonValueType(type)), - TYPES.filter(type => type.isValueType).map(type => transient(type)), ), ).trimEnd(), '}', diff --git a/scripts/generate/templates/TransientSlot.js b/scripts/generate/templates/TransientSlot.js new file mode 100644 index 00000000000..8e291bc13da --- /dev/null +++ b/scripts/generate/templates/TransientSlot.js @@ -0,0 +1,80 @@ +const format = require('../format-lines'); +const { TYPES } = require('./Slot.opts'); + +const header = `\ +pragma solidity ^0.8.24; + +/** + * @dev Library for reading and writing value-types to specific transient storage slots. + * + * Transient slots are often used to store temporary values that are removed after the current transaction. + * This library helps with reading and writing to such slots without the need for inline assembly. + * + * * Example reading and writing values using transient storage: + * \`\`\`solidity + * contract Lock { + * using TransientSlot for *; + * + * // Define the slot. Alternatively, use the SlotDerivation library to derive the slot. + * bytes32 internal constant _LOCK_SLOT = 0xf4678858b2b588224636b8522b729e7722d32fc491da849ed75b3fdf3c84f542; + * + * modifier locked() { + * require(!_LOCK_SLOT.asBoolean().tload()); + * + * _LOCK_SLOT.asBoolean().tstore(true); + * _; + * _LOCK_SLOT.asBoolean().tstore(false); + * } + * } + * \`\`\` + * + * TIP: Consider using this library along with {SlotDerivation}. + */ +`; + +const udvt = ({ type, name }) => `\ +/** + * @dev UDVT that represent a slot holding a ${type}. + */ +type ${name}Slot is bytes32; + +/** + * @dev Cast an arbitrary slot to a ${name}Slot. + */ +function as${name}(bytes32 slot) internal pure returns (${name}Slot) { + return ${name}Slot.wrap(slot); +} +`; + +const transient = ({ type, name }) => `\ +/** + * @dev Load the value held at location \`slot\` in transient storage. + */ +function tload(${name}Slot slot) internal view returns (${type} value) { + assembly ("memory-safe") { + value := tload(slot) + } +} + +/** + * @dev Store \`value\` at location \`slot\` in transient storage. + */ +function tstore(${name}Slot slot, ${type} value) internal { + assembly ("memory-safe") { + tstore(slot, value) + } +} +`; + +// GENERATE +module.exports = format( + header.trimEnd(), + 'library TransientSlot {', + format( + [].concat( + TYPES.filter(type => type.isValueType).map(type => udvt(type)), + TYPES.filter(type => type.isValueType).map(type => transient(type)), + ), + ).trimEnd(), + '}', +); diff --git a/scripts/generate/templates/TransientSlotMock.js b/scripts/generate/templates/TransientSlotMock.js new file mode 100644 index 00000000000..4807b0cc1ff --- /dev/null +++ b/scripts/generate/templates/TransientSlotMock.js @@ -0,0 +1,35 @@ +const format = require('../format-lines'); +const { TYPES } = require('./Slot.opts'); + +const header = `\ +pragma solidity ^0.8.24; + +import {Multicall} from "../utils/Multicall.sol"; +import {TransientSlot} from "../utils/TransientSlot.sol"; +`; + +const transient = ({ type, name }) => `\ +event ${name}Value(bytes32 slot, ${type} value); + +function tload${name}(bytes32 slot) public { + emit ${name}Value(slot, slot.as${name}().tload()); +} + +function tstore(bytes32 slot, ${type} value) public { + slot.as${name}().tstore(value); +} +`; + +// GENERATE +module.exports = format( + header, + 'contract TransientSlotMock is Multicall {', + format( + [].concat( + 'using TransientSlot for *;', + '', + TYPES.filter(type => type.isValueType).map(type => transient(type)), + ), + ).trimEnd(), + '}', +); diff --git a/test/utils/StorageSlot.test.js b/test/utils/StorageSlot.test.js index 35e83e29e63..ddcf305d1a7 100644 --- a/test/utils/StorageSlot.test.js +++ b/test/utils/StorageSlot.test.js @@ -70,37 +70,4 @@ describe('StorageSlot', function () { }); }); } - - for (const { name, type, value, zero } of TYPES.filter(type => type.isValueType)) { - describe(`${type} transient slot`, function () { - const load = `tload${name}(bytes32)`; - const store = `tstore(bytes32,${type})`; - const event = `${name}Value`; - - it('load', async function () { - await expect(this.mock[load](slot)).to.emit(this.mock, event).withArgs(slot, zero); - }); - - it('store and load (2 txs)', async function () { - await this.mock[store](slot, value); - await expect(this.mock[load](slot)).to.emit(this.mock, event).withArgs(slot, zero); - }); - - it('store and load (batched)', async function () { - await expect( - this.mock.multicall([ - this.mock.interface.encodeFunctionData(store, [slot, value]), - this.mock.interface.encodeFunctionData(load, [slot]), - this.mock.interface.encodeFunctionData(load, [otherSlot]), - ]), - ) - .to.emit(this.mock, event) - .withArgs(slot, value) - .to.emit(this.mock, event) - .withArgs(otherSlot, zero); - - await expect(this.mock[load](slot)).to.emit(this.mock, event).withArgs(slot, zero); - }); - }); - } }); diff --git a/test/utils/TransientSlot.test.js b/test/utils/TransientSlot.test.js new file mode 100644 index 00000000000..7b70be375d4 --- /dev/null +++ b/test/utils/TransientSlot.test.js @@ -0,0 +1,59 @@ +const { ethers } = require('hardhat'); +const { expect } = require('chai'); +const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); +const { generators } = require('../helpers/random'); + +const slot = ethers.id('some.storage.slot'); +const otherSlot = ethers.id('some.other.storage.slot'); + +// Non-value types are not supported by the `TransientSlot` library. +const TYPES = [ + { name: 'Boolean', type: 'bool', value: true, zero: false }, + { name: 'Address', type: 'address', value: generators.address(), zero: generators.address.zero }, + { name: 'Bytes32', type: 'bytes32', value: generators.bytes32(), zero: generators.bytes32.zero }, + { name: 'Uint256', type: 'uint256', value: generators.uint256(), zero: generators.uint256.zero }, + { name: 'Int256', type: 'int256', value: generators.int256(), zero: generators.int256.zero }, +]; + +async function fixture() { + return { mock: await ethers.deployContract('TransientSlotMock') }; +} + +describe('TransientSlot', function () { + beforeEach(async function () { + Object.assign(this, await loadFixture(fixture)); + }); + + for (const { name, type, value, zero } of TYPES) { + describe(`${type} transient slot`, function () { + const load = `tload${name}(bytes32)`; + const store = `tstore(bytes32,${type})`; + const event = `${name}Value`; + + it('load', async function () { + await expect(this.mock[load](slot)).to.emit(this.mock, event).withArgs(slot, zero); + }); + + it('store and load (2 txs)', async function () { + await this.mock[store](slot, value); + await expect(this.mock[load](slot)).to.emit(this.mock, event).withArgs(slot, zero); + }); + + it('store and load (batched)', async function () { + await expect( + this.mock.multicall([ + this.mock.interface.encodeFunctionData(store, [slot, value]), + this.mock.interface.encodeFunctionData(load, [slot]), + this.mock.interface.encodeFunctionData(load, [otherSlot]), + ]), + ) + .to.emit(this.mock, event) + .withArgs(slot, value) + .to.emit(this.mock, event) + .withArgs(otherSlot, zero); + + await expect(this.mock[load](slot)).to.emit(this.mock, event).withArgs(slot, zero); + }); + }); + } +}); From 72c152dc1c41f23d7c504e175f5b417fccc89426 Mon Sep 17 00:00:00 2001 From: Elias Rad <146735585+nnsW3@users.noreply.github.com> Date: Thu, 10 Oct 2024 07:23:01 +0300 Subject: [PATCH 27/84] Fix spelling issues in documentation (#5235) --- docs/modules/ROOT/pages/erc1155.adoc | 2 +- docs/modules/ROOT/pages/erc4626.adoc | 4 ++-- docs/modules/ROOT/pages/governance.adoc | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/modules/ROOT/pages/erc1155.adoc b/docs/modules/ROOT/pages/erc1155.adoc index 5bfb49accd4..7f00f3ea4b1 100644 --- a/docs/modules/ROOT/pages/erc1155.adoc +++ b/docs/modules/ROOT/pages/erc1155.adoc @@ -108,7 +108,7 @@ ERC1155InvalidReceiver("

") This is a good thing! It means that the recipient contract has not registered itself as aware of the ERC-1155 protocol, so transfers to it are disabled to *prevent tokens from being locked forever*. As an example, https://etherscan.io/token/0xa74476443119A942dE498590Fe1f2454d7D4aC0d?a=0xa74476443119A942dE498590Fe1f2454d7D4aC0d[the Golem contract currently holds over 350k `GNT` tokens], worth multiple tens of thousands of dollars, and lacks methods to get them out of there. This has happened to virtually every ERC20-backed project, usually due to user error. -In order for our contract to receive ERC-1155 tokens we can inherit from the convenience contract xref:api:token/ERC1155.adoc#ERC1155Holder[`ERC1155Holder`] which handles the registering for us. Though we need to remember to implement functionality to allow tokens to be transferred out of our contract: +In order for our contract to receive ERC-1155 tokens we can inherit from the convenience contract xref:api:token/ERC1155.adoc#ERC1155Holder[`ERC1155Holder`] which handles the registering for us. However, we need to remember to implement functionality to allow tokens to be transferred out of our contract: [source,solidity] ---- diff --git a/docs/modules/ROOT/pages/erc4626.adoc b/docs/modules/ROOT/pages/erc4626.adoc index 79388c0a2e7..c219595dd5c 100644 --- a/docs/modules/ROOT/pages/erc4626.adoc +++ b/docs/modules/ROOT/pages/erc4626.adoc @@ -29,7 +29,7 @@ image::erc4626-rate-loglogext.png[More exchange rates in logarithmic scale] === The attack -When depositing tokens, the number of shares a user gets is rounded towards zero. This rounding takes away value from the user in favor of the vault (i.e. in favor of all the current share holders). This rounding is often negligible because of the amount at stake. If you deposit 1e9 shares worth of tokens, the rounding will have you lose at most 0.0000001% of your deposit. However if you deposit 10 shares worth of tokens, you could lose 10% of your deposit. Even worse, if you deposit <1 share worth of tokens, then you get 0 shares, and you basically made a donation. +When depositing tokens, the number of shares a user gets is rounded towards zero. This rounding takes away value from the user in favor of the vault (i.e. in favor of all the current shareholders). This rounding is often negligible because of the amount at stake. If you deposit 1e9 shares worth of tokens, the rounding will have you lose at most 0.0000001% of your deposit. However if you deposit 10 shares worth of tokens, you could lose 10% of your deposit. Even worse, if you deposit <1 share worth of tokens, then you get 0 shares, and you basically made a donation. For a given amount of assets, the more shares you receive the safer you are. If you want to limit your losses to at most 1%, you need to receive at least 100 shares. @@ -47,7 +47,7 @@ The idea of an inflation attack is that an attacker can donate assets to the vau image::erc4626-attack.png[Inflation attack without protection] -Figure 6 shows how an attacker can manipulate the rate of an empty vault. First the attacker must deposit a small amount of tokens (1 token) and follow up with a donation of 1e5 tokens directly to the vault to move the exchange rate "right". This puts the vault in a state where any deposit smaller than 1e5 would be completely lost to the vault. Given that the attacker is the only share holder (from their donation), the attacker would steal all the tokens deposited. +Figure 6 shows how an attacker can manipulate the rate of an empty vault. First the attacker must deposit a small amount of tokens (1 token) and follow up with a donation of 1e5 tokens directly to the vault to move the exchange rate "right". This puts the vault in a state where any deposit smaller than 1e5 would be completely lost to the vault. Given that the attacker is the only shareholder (from their donation), the attacker would steal all the tokens deposited. An attacker would typically wait for a user to do the first deposit into the vault, and would frontrun that operation with the attack described above. The risk is low, and the size of the "donation" required to manipulate the vault is equivalent to the size of the deposit that is being attacked. diff --git a/docs/modules/ROOT/pages/governance.adoc b/docs/modules/ROOT/pages/governance.adoc index 19f23d78d92..5f8e77555aa 100644 --- a/docs/modules/ROOT/pages/governance.adoc +++ b/docs/modules/ROOT/pages/governance.adoc @@ -74,7 +74,7 @@ votingPeriod: How long does a proposal remain open to votes. These parameters are specified in the unit defined in the token's clock. Assuming the token uses block numbers, and assuming block time of around 12 seconds, we will have set votingDelay = 1 day = 7200 blocks, and votingPeriod = 1 week = 50400 blocks. -We can optionally set a proposal threshold as well. This restricts proposal creation to accounts who have enough voting power. +We can optionally set a proposal threshold as well. This restricts proposal creation to accounts that have enough voting power. ```solidity include::api:example$governance/MyGovernor.sol[] From bd588959adda3ebdffb5783bef7fe0a5af6e1e17 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Mon, 14 Oct 2024 17:13:33 +0200 Subject: [PATCH 28/84] Add toUint, toInt and hexToUint to Strings (#5166) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: cairo Co-authored-by: Ernesto García --- .changeset/eighty-hounds-promise.md | 5 + contracts/governance/Governor.sol | 82 +++----- contracts/utils/README.adoc | 2 +- contracts/utils/Strings.sol | 284 ++++++++++++++++++++++++++++ test/utils/Strings.t.sol | 27 +++ test/utils/Strings.test.js | 171 +++++++++++++++-- 6 files changed, 503 insertions(+), 68 deletions(-) create mode 100644 .changeset/eighty-hounds-promise.md create mode 100644 test/utils/Strings.t.sol diff --git a/.changeset/eighty-hounds-promise.md b/.changeset/eighty-hounds-promise.md new file mode 100644 index 00000000000..3727a6515f0 --- /dev/null +++ b/.changeset/eighty-hounds-promise.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': minor +--- + +`Strings`: Add `parseUint`, `parseInt`, `parseHexUint` and `parseAddress` to parse strings into numbers and addresses. Also provide variants of these functions that parse substrings, and `tryXxx` variants that do not revert on invalid input. diff --git a/contracts/governance/Governor.sol b/contracts/governance/Governor.sol index 02adffcb39b..f1851b30ee6 100644 --- a/contracts/governance/Governor.sol +++ b/contracts/governance/Governor.sol @@ -13,6 +13,7 @@ import {DoubleEndedQueue} from "../utils/structs/DoubleEndedQueue.sol"; import {Address} from "../utils/Address.sol"; import {Context} from "../utils/Context.sol"; import {Nonces} from "../utils/Nonces.sol"; +import {Strings} from "../utils/Strings.sol"; import {IGovernor, IERC6372} from "./IGovernor.sol"; /** @@ -760,67 +761,25 @@ abstract contract Governor is Context, ERC165, EIP712, Nonces, IGovernor, IERC72 address proposer, string memory description ) internal view virtual returns (bool) { - uint256 len = bytes(description).length; - - // Length is too short to contain a valid proposer suffix - if (len < 52) { - return true; - } - - // Extract what would be the `#proposer=0x` marker beginning the suffix - bytes12 marker; - assembly ("memory-safe") { - // - Start of the string contents in memory = description + 32 - // - First character of the marker = len - 52 - // - Length of "#proposer=0x0000000000000000000000000000000000000000" = 52 - // - We read the memory word starting at the first character of the marker: - // - (description + 32) + (len - 52) = description + (len - 20) - // - Note: Solidity will ignore anything past the first 12 bytes - marker := mload(add(description, sub(len, 20))) - } - - // If the marker is not found, there is no proposer suffix to check - if (marker != bytes12("#proposer=0x")) { - return true; - } + unchecked { + uint256 length = bytes(description).length; - // Parse the 40 characters following the marker as uint160 - uint160 recovered = 0; - for (uint256 i = len - 40; i < len; ++i) { - (bool isHex, uint8 value) = _tryHexToUint(bytes(description)[i]); - // If any of the characters is not a hex digit, ignore the suffix entirely - if (!isHex) { + // Length is too short to contain a valid proposer suffix + if (length < 52) { return true; } - recovered = (recovered << 4) | value; - } - return recovered == uint160(proposer); - } + // Extract what would be the `#proposer=` marker beginning the suffix + bytes10 marker = bytes10(_unsafeReadBytesOffset(bytes(description), length - 52)); - /** - * @dev Try to parse a character from a string as a hex value. Returns `(true, value)` if the char is in - * `[0-9a-fA-F]` and `(false, 0)` otherwise. Value is guaranteed to be in the range `0 <= value < 16` - */ - function _tryHexToUint(bytes1 char) private pure returns (bool isHex, uint8 value) { - uint8 c = uint8(char); - unchecked { - // Case 0-9 - if (47 < c && c < 58) { - return (true, c - 48); - } - // Case A-F - else if (64 < c && c < 71) { - return (true, c - 55); - } - // Case a-f - else if (96 < c && c < 103) { - return (true, c - 87); - } - // Else: not a hex char - else { - return (false, 0); + // If the marker is not found, there is no proposer suffix to check + if (marker != bytes10("#proposer=")) { + return true; } + + // Check that the last 42 characters (after the marker) are a properly formatted address. + (bool success, address recovered) = Strings.tryParseAddress(description, length - 42, length); + return !success || recovered == proposer; } } @@ -849,4 +808,17 @@ abstract contract Governor is Context, ERC165, EIP712, Nonces, IGovernor, IERC72 * @inheritdoc IGovernor */ function quorum(uint256 timepoint) public view virtual returns (uint256); + + /** + * @dev Reads a bytes32 from a bytes array without bounds checking. + * + * NOTE: making this function internal would mean it could be used with memory unsafe offset, and marking the + * assembly block as such would prevent some optimizations. + */ + function _unsafeReadBytesOffset(bytes memory buffer, uint256 offset) private pure returns (bytes32 value) { + // This is not memory safe in the general case, but all calls to this private function are within bounds. + assembly ("memory-safe") { + value := mload(add(buffer, add(0x20, offset))) + } + } } diff --git a/contracts/utils/README.adoc b/contracts/utils/README.adoc index 24b95b4e6f8..245c89c0486 100644 --- a/contracts/utils/README.adoc +++ b/contracts/utils/README.adoc @@ -34,7 +34,7 @@ Miscellaneous contracts and libraries containing utility functions you can use t * {Strings}: Common operations for strings formatting. * {ShortString}: Library to encode (and decode) short strings into (or from) a single bytes32 slot for optimizing costs. Short strings are limited to 31 characters. * {SlotDerivation}: Methods for deriving storage slot from ERC-7201 namespaces as well as from constructions such as mapping and arrays. - * {StorageSlot}: Methods for accessing specific storage slots formatted as common primitive types. + * {StorageSlot}: Methods for accessing specific storage slots formatted as common primitive types. * {TransientSlot}: Primitives for reading from and writing to transient storage (only value types are currently supported). * {Multicall}: Abstract contract with a utility to allow batching together multiple calls in a single transaction. Useful for allowing EOAs to perform multiple operations at once. * {Context}: A utility for abstracting the sender and calldata in the current execution context. diff --git a/contracts/utils/Strings.sol b/contracts/utils/Strings.sol index 5448060b70e..b72588646f7 100644 --- a/contracts/utils/Strings.sol +++ b/contracts/utils/Strings.sol @@ -4,12 +4,15 @@ pragma solidity ^0.8.20; import {Math} from "./math/Math.sol"; +import {SafeCast} from "./math/SafeCast.sol"; import {SignedMath} from "./math/SignedMath.sol"; /** * @dev String operations. */ library Strings { + using SafeCast for *; + bytes16 private constant HEX_DIGITS = "0123456789abcdef"; uint8 private constant ADDRESS_LENGTH = 20; @@ -18,6 +21,16 @@ library Strings { */ error StringsInsufficientHexLength(uint256 value, uint256 length); + /** + * @dev The string being parsed contains characters that are not in scope of the given base. + */ + error StringsInvalidChar(); + + /** + * @dev The string being parsed is not a properly formatted address. + */ + error StringsInvalidAddressFormat(); + /** * @dev Converts a `uint256` to its ASCII `string` decimal representation. */ @@ -113,4 +126,275 @@ library Strings { function equal(string memory a, string memory b) internal pure returns (bool) { return bytes(a).length == bytes(b).length && keccak256(bytes(a)) == keccak256(bytes(b)); } + + /** + * @dev Parse a decimal string and returns the value as a `uint256`. + * + * Requirements: + * - The string must be formatted as `[0-9]*` + * - The result must fit into an `uint256` type + */ + function parseUint(string memory input) internal pure returns (uint256) { + return parseUint(input, 0, bytes(input).length); + } + + /** + * @dev Variant of {parseUint} that parses a substring of `input` located between position `begin` (included) and + * `end` (excluded). + * + * Requirements: + * - The substring must be formatted as `[0-9]*` + * - The result must fit into an `uint256` type + */ + function parseUint(string memory input, uint256 begin, uint256 end) internal pure returns (uint256) { + (bool success, uint256 value) = tryParseUint(input, begin, end); + if (!success) revert StringsInvalidChar(); + return value; + } + + /** + * @dev Variant of {parseUint-string} that returns false if the parsing fails because of an invalid character. + * + * NOTE: This function will revert if the result does not fit in a `uint256`. + */ + function tryParseUint(string memory input) internal pure returns (bool success, uint256 value) { + return tryParseUint(input, 0, bytes(input).length); + } + + /** + * @dev Variant of {parseUint-string-uint256-uint256} that returns false if the parsing fails because of an invalid + * character. + * + * NOTE: This function will revert if the result does not fit in a `uint256`. + */ + function tryParseUint( + string memory input, + uint256 begin, + uint256 end + ) internal pure returns (bool success, uint256 value) { + bytes memory buffer = bytes(input); + + uint256 result = 0; + for (uint256 i = begin; i < end; ++i) { + uint8 chr = _tryParseChr(bytes1(_unsafeReadBytesOffset(buffer, i))); + if (chr > 9) return (false, 0); + result *= 10; + result += chr; + } + return (true, result); + } + + /** + * @dev Parse a decimal string and returns the value as a `int256`. + * + * Requirements: + * - The string must be formatted as `[-+]?[0-9]*` + * - The result must fit in an `int256` type. + */ + function parseInt(string memory input) internal pure returns (int256) { + return parseInt(input, 0, bytes(input).length); + } + + /** + * @dev Variant of {parseInt-string} that parses a substring of `input` located between position `begin` (included) and + * `end` (excluded). + * + * Requirements: + * - The substring must be formatted as `[-+]?[0-9]*` + * - The result must fit in an `int256` type. + */ + function parseInt(string memory input, uint256 begin, uint256 end) internal pure returns (int256) { + (bool success, int256 value) = tryParseInt(input, begin, end); + if (!success) revert StringsInvalidChar(); + return value; + } + + /** + * @dev Variant of {parseInt-string} that returns false if the parsing fails because of an invalid character or if + * the result does not fit in a `int256`. + * + * NOTE: This function will revert if the absolute value of the result does not fit in a `uint256`. + */ + function tryParseInt(string memory input) internal pure returns (bool success, int256 value) { + return tryParseInt(input, 0, bytes(input).length); + } + + uint256 private constant ABS_MIN_INT256 = 2 ** 255; + + /** + * @dev Variant of {parseInt-string-uint256-uint256} that returns false if the parsing fails because of an invalid + * character or if the result does not fit in a `int256`. + * + * NOTE: This function will revert if the absolute value of the result does not fit in a `uint256`. + */ + function tryParseInt( + string memory input, + uint256 begin, + uint256 end + ) internal pure returns (bool success, int256 value) { + bytes memory buffer = bytes(input); + + // Check presence of a negative sign. + bytes1 sign = bytes1(_unsafeReadBytesOffset(buffer, begin)); + bool positiveSign = sign == bytes1("+"); + bool negativeSign = sign == bytes1("-"); + uint256 offset = (positiveSign || negativeSign).toUint(); + + (bool absSuccess, uint256 absValue) = tryParseUint(input, begin + offset, end); + + if (absSuccess && absValue < ABS_MIN_INT256) { + return (true, negativeSign ? -int256(absValue) : int256(absValue)); + } else if (absSuccess && negativeSign && absValue == ABS_MIN_INT256) { + return (true, type(int256).min); + } else return (false, 0); + } + + /** + * @dev Parse a hexadecimal string (with or without "0x" prefix), and returns the value as a `uint256`. + * + * Requirements: + * - The string must be formatted as `(0x)?[0-9a-fA-F]*` + * - The result must fit in an `uint256` type. + */ + function parseHexUint(string memory input) internal pure returns (uint256) { + return parseHexUint(input, 0, bytes(input).length); + } + + /** + * @dev Variant of {parseHexUint} that parses a substring of `input` located between position `begin` (included) and + * `end` (excluded). + * + * Requirements: + * - The substring must be formatted as `(0x)?[0-9a-fA-F]*` + * - The result must fit in an `uint256` type. + */ + function parseHexUint(string memory input, uint256 begin, uint256 end) internal pure returns (uint256) { + (bool success, uint256 value) = tryParseHexUint(input, begin, end); + if (!success) revert StringsInvalidChar(); + return value; + } + + /** + * @dev Variant of {parseHexUint-string} that returns false if the parsing fails because of an invalid character. + * + * NOTE: This function will revert if the result does not fit in a `uint256`. + */ + function tryParseHexUint(string memory input) internal pure returns (bool success, uint256 value) { + return tryParseHexUint(input, 0, bytes(input).length); + } + + /** + * @dev Variant of {parseHexUint-string-uint256-uint256} that returns false if the parsing fails because of an + * invalid character. + * + * NOTE: This function will revert if the result does not fit in a `uint256`. + */ + function tryParseHexUint( + string memory input, + uint256 begin, + uint256 end + ) internal pure returns (bool success, uint256 value) { + bytes memory buffer = bytes(input); + + // skip 0x prefix if present + bool hasPrefix = bytes2(_unsafeReadBytesOffset(buffer, begin)) == bytes2("0x"); + uint256 offset = hasPrefix.toUint() * 2; + + uint256 result = 0; + for (uint256 i = begin + offset; i < end; ++i) { + uint8 chr = _tryParseChr(bytes1(_unsafeReadBytesOffset(buffer, i))); + if (chr > 15) return (false, 0); + result *= 16; + unchecked { + // Multiplying by 16 is equivalent to a shift of 4 bits (with additional overflow check). + // This guaratees that adding a value < 16 will not cause an overflow, hence the unchecked. + result += chr; + } + } + return (true, result); + } + + /** + * @dev Parse a hexadecimal string (with or without "0x" prefix), and returns the value as an `address`. + * + * Requirements: + * - The string must be formatted as `(0x)?[0-9a-fA-F]{40}` + */ + function parseAddress(string memory input) internal pure returns (address) { + return parseAddress(input, 0, bytes(input).length); + } + + /** + * @dev Variant of {parseAddress} that parses a substring of `input` located between position `begin` (included) and + * `end` (excluded). + * + * Requirements: + * - The substring must be formatted as `(0x)?[0-9a-fA-F]{40}` + */ + function parseAddress(string memory input, uint256 begin, uint256 end) internal pure returns (address) { + (bool success, address value) = tryParseAddress(input, begin, end); + if (!success) revert StringsInvalidAddressFormat(); + return value; + } + + /** + * @dev Variant of {parseAddress-string} that returns false if the parsing fails because the input is not a properly + * formatted address. See {parseAddress} requirements. + */ + function tryParseAddress(string memory input) internal pure returns (bool success, address value) { + return tryParseAddress(input, 0, bytes(input).length); + } + + /** + * @dev Variant of {parseAddress-string-uint256-uint256} that returns false if the parsing fails because input is not a properly + * formatted address. See {parseAddress} requirements. + */ + function tryParseAddress( + string memory input, + uint256 begin, + uint256 end + ) internal pure returns (bool success, address value) { + // check that input is the correct length + bool hasPrefix = bytes2(_unsafeReadBytesOffset(bytes(input), begin)) == bytes2("0x"); + uint256 expectedLength = 40 + hasPrefix.toUint() * 2; + + if (end - begin == expectedLength) { + // length guarantees that this does not overflow, and value is at most type(uint160).max + (bool s, uint256 v) = tryParseHexUint(input, begin, end); + return (s, address(uint160(v))); + } else { + return (false, address(0)); + } + } + + function _tryParseChr(bytes1 chr) private pure returns (uint8) { + uint8 value = uint8(chr); + + // Try to parse `chr`: + // - Case 1: [0-9] + // - Case 2: [a-f] + // - Case 3: [A-F] + // - otherwise not supported + unchecked { + if (value > 47 && value < 58) value -= 48; + else if (value > 96 && value < 103) value -= 87; + else if (value > 64 && value < 71) value -= 55; + else return type(uint8).max; + } + + return value; + } + + /** + * @dev Reads a bytes32 from a bytes array without bounds checking. + * + * NOTE: making this function internal would mean it could be used with memory unsafe offset, and marking the + * assembly block as such would prevent some optimizations. + */ + function _unsafeReadBytesOffset(bytes memory buffer, uint256 offset) private pure returns (bytes32 value) { + // This is not memory safe in the general case, but all calls to this private function are within bounds. + assembly ("memory-safe") { + value := mload(add(buffer, add(0x20, offset))) + } + } } diff --git a/test/utils/Strings.t.sol b/test/utils/Strings.t.sol new file mode 100644 index 00000000000..b3eb67a5cd2 --- /dev/null +++ b/test/utils/Strings.t.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {Test} from "forge-std/Test.sol"; + +import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; + +contract StringsTest is Test { + using Strings for *; + + function testParse(uint256 value) external { + assertEq(value, value.toString().parseUint()); + } + + function testParseSigned(int256 value) external { + assertEq(value, value.toStringSigned().parseInt()); + } + + function testParseHex(uint256 value) external { + assertEq(value, value.toHexString().parseHexUint()); + } + + function testParseChecksumHex(address value) external { + assertEq(value, value.toChecksumHexString().parseAddress()); + } +} diff --git a/test/utils/Strings.test.js b/test/utils/Strings.test.js index 6353fd886db..5a47d4d10de 100644 --- a/test/utils/Strings.test.js +++ b/test/utils/Strings.test.js @@ -1,6 +1,7 @@ const { ethers } = require('hardhat'); const { expect } = require('chai'); const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); +const { PANIC_CODES } = require('@nomicfoundation/hardhat-chai-matchers/panic'); async function fixture() { const mock = await ethers.deployContract('$Strings'); @@ -38,11 +39,15 @@ describe('Strings', function () { it('converts MAX_UINT256', async function () { const value = ethers.MaxUint256; expect(await this.mock.$toString(value)).to.equal(value.toString(10)); + expect(await this.mock.$parseUint(value.toString(10))).to.equal(value); + expect(await this.mock.$tryParseUint(value.toString(10))).to.deep.equal([true, value]); }); for (const value of values) { it(`converts ${value}`, async function () { - expect(await this.mock.$toString(value)).to.equal(value); + expect(await this.mock.$toString(value)).to.equal(value.toString(10)); + expect(await this.mock.$parseUint(value.toString(10))).to.equal(value); + expect(await this.mock.$tryParseUint(value.toString(10))).to.deep.equal([true, value]); }); } }); @@ -51,21 +56,29 @@ describe('Strings', function () { it('converts MAX_INT256', async function () { const value = ethers.MaxInt256; expect(await this.mock.$toStringSigned(value)).to.equal(value.toString(10)); + expect(await this.mock.$parseInt(value.toString(10))).to.equal(value); + expect(await this.mock.$tryParseInt(value.toString(10))).to.deep.equal([true, value]); }); it('converts MIN_INT256', async function () { const value = ethers.MinInt256; expect(await this.mock.$toStringSigned(value)).to.equal(value.toString(10)); + expect(await this.mock.$parseInt(value.toString(10))).to.equal(value); + expect(await this.mock.$tryParseInt(value.toString(10))).to.deep.equal([true, value]); }); for (const value of values) { it(`convert ${value}`, async function () { - expect(await this.mock.$toStringSigned(value)).to.equal(value); + expect(await this.mock.$toStringSigned(value)).to.equal(value.toString(10)); + expect(await this.mock.$parseInt(value.toString(10))).to.equal(value); + expect(await this.mock.$tryParseInt(value.toString(10))).to.deep.equal([true, value]); }); it(`convert negative ${value}`, async function () { const negated = -value; expect(await this.mock.$toStringSigned(negated)).to.equal(negated.toString(10)); + expect(await this.mock.$parseInt(negated.toString(10))).to.equal(negated); + expect(await this.mock.$tryParseInt(negated.toString(10))).to.deep.equal([true, negated]); }); } }); @@ -73,17 +86,36 @@ describe('Strings', function () { describe('toHexString', function () { it('converts 0', async function () { - expect(await this.mock.getFunction('$toHexString(uint256)')(0n)).to.equal('0x00'); + const value = 0n; + const string = ethers.toBeHex(value); // 0x00 + + expect(await this.mock.getFunction('$toHexString(uint256)')(value)).to.equal(string); + expect(await this.mock.$parseHexUint(string)).to.equal(value); + expect(await this.mock.$parseHexUint(string.replace(/0x/, ''))).to.equal(value); + expect(await this.mock.$tryParseHexUint(string)).to.deep.equal([true, value]); + expect(await this.mock.$tryParseHexUint(string.replace(/0x/, ''))).to.deep.equal([true, value]); }); it('converts a positive number', async function () { - expect(await this.mock.getFunction('$toHexString(uint256)')(0x4132n)).to.equal('0x4132'); + const value = 0x4132n; + const string = ethers.toBeHex(value); + + expect(await this.mock.getFunction('$toHexString(uint256)')(value)).to.equal(string); + expect(await this.mock.$parseHexUint(string)).to.equal(value); + expect(await this.mock.$parseHexUint(string.replace(/0x/, ''))).to.equal(value); + expect(await this.mock.$tryParseHexUint(string)).to.deep.equal([true, value]); + expect(await this.mock.$tryParseHexUint(string.replace(/0x/, ''))).to.deep.equal([true, value]); }); it('converts MAX_UINT256', async function () { - expect(await this.mock.getFunction('$toHexString(uint256)')(ethers.MaxUint256)).to.equal( - `0x${ethers.MaxUint256.toString(16)}`, - ); + const value = ethers.MaxUint256; + const string = ethers.toBeHex(value); + + expect(await this.mock.getFunction('$toHexString(uint256)')(value)).to.equal(string); + expect(await this.mock.$parseHexUint(string)).to.equal(value); + expect(await this.mock.$parseHexUint(string.replace(/0x/, ''))).to.equal(value); + expect(await this.mock.$tryParseHexUint(string)).to.deep.equal([true, value]); + expect(await this.mock.$tryParseHexUint(string.replace(/0x/, ''))).to.deep.equal([true, value]); }); }); @@ -97,13 +129,13 @@ describe('Strings', function () { it('converts a positive number (short)', async function () { const length = 1n; await expect(this.mock.getFunction('$toHexString(uint256,uint256)')(0x4132n, length)) - .to.be.revertedWithCustomError(this.mock, `StringsInsufficientHexLength`) + .to.be.revertedWithCustomError(this.mock, 'StringsInsufficientHexLength') .withArgs(0x4132, length); }); it('converts MAX_UINT256', async function () { expect(await this.mock.getFunction('$toHexString(uint256,uint256)')(ethers.MaxUint256, 32n)).to.equal( - `0x${ethers.MaxUint256.toString(16)}`, + ethers.toBeHex(ethers.MaxUint256), ); }); }); @@ -139,9 +171,16 @@ describe('Strings', function () { describe('toChecksumHexString', function () { for (const addr of addresses) { it(`converts ${addr}`, async function () { - expect(await this.mock.getFunction('$toChecksumHexString(address)')(addr)).to.equal( - ethers.getAddress(addr.toLowerCase()), - ); + expect(await this.mock.$toChecksumHexString(addr)).to.equal(ethers.getAddress(addr)); + }); + } + }); + + describe('parseAddress', function () { + for (const addr of addresses) { + it(`converts ${addr}`, async function () { + expect(await this.mock.$parseAddress(addr)).to.equal(ethers.getAddress(addr)); + expect(await this.mock.$tryParseAddress(addr)).to.deep.equal([true, ethers.getAddress(addr)]); }); } }); @@ -177,4 +216,112 @@ describe('Strings', function () { expect(await this.mock.$equal(str1, str2)).to.be.true; }); }); + + describe('Edge cases: invalid parsing', function () { + it('parseUint overflow', async function () { + await expect(this.mock.$parseUint((ethers.MaxUint256 + 1n).toString(10))).to.be.revertedWithPanic( + PANIC_CODES.ARITHMETIC_OVERFLOW, + ); + await expect(this.mock.$tryParseUint((ethers.MaxUint256 + 1n).toString(10))).to.be.revertedWithPanic( + PANIC_CODES.ARITHMETIC_OVERFLOW, + ); + }); + + it('parseUint invalid character', async function () { + await expect(this.mock.$parseUint('0x1')).to.be.revertedWithCustomError(this.mock, 'StringsInvalidChar'); + await expect(this.mock.$parseUint('1f')).to.be.revertedWithCustomError(this.mock, 'StringsInvalidChar'); + await expect(this.mock.$parseUint('-10')).to.be.revertedWithCustomError(this.mock, 'StringsInvalidChar'); + await expect(this.mock.$parseUint('1.0')).to.be.revertedWithCustomError(this.mock, 'StringsInvalidChar'); + await expect(this.mock.$parseUint('1 000')).to.be.revertedWithCustomError(this.mock, 'StringsInvalidChar'); + expect(await this.mock.$tryParseUint('0x1')).to.deep.equal([false, 0n]); + expect(await this.mock.$tryParseUint('1f')).to.deep.equal([false, 0n]); + expect(await this.mock.$tryParseUint('-10')).to.deep.equal([false, 0n]); + expect(await this.mock.$tryParseUint('1.0')).deep.equal([false, 0n]); + expect(await this.mock.$tryParseUint('1 000')).deep.equal([false, 0n]); + }); + + it('parseInt overflow', async function () { + await expect(this.mock.$parseInt((ethers.MaxUint256 + 1n).toString(10))).to.be.revertedWithPanic( + PANIC_CODES.ARITHMETIC_OVERFLOW, + ); + await expect(this.mock.$parseInt((-ethers.MaxUint256 - 1n).toString(10))).to.be.revertedWithPanic( + PANIC_CODES.ARITHMETIC_OVERFLOW, + ); + await expect(this.mock.$tryParseInt((ethers.MaxUint256 + 1n).toString(10))).to.be.revertedWithPanic( + PANIC_CODES.ARITHMETIC_OVERFLOW, + ); + await expect(this.mock.$tryParseInt((-ethers.MaxUint256 - 1n).toString(10))).to.be.revertedWithPanic( + PANIC_CODES.ARITHMETIC_OVERFLOW, + ); + await expect(this.mock.$parseInt((ethers.MaxInt256 + 1n).toString(10))).to.be.revertedWithCustomError( + this.mock, + 'StringsInvalidChar', + ); + await expect(this.mock.$parseInt((ethers.MinInt256 - 1n).toString(10))).to.be.revertedWithCustomError( + this.mock, + 'StringsInvalidChar', + ); + expect(await this.mock.$tryParseInt((ethers.MaxInt256 + 1n).toString(10))).to.deep.equal([false, 0n]); + expect(await this.mock.$tryParseInt((ethers.MinInt256 - 1n).toString(10))).to.deep.equal([false, 0n]); + }); + + it('parseInt invalid character', async function () { + await expect(this.mock.$parseInt('0x1')).to.be.revertedWithCustomError(this.mock, 'StringsInvalidChar'); + await expect(this.mock.$parseInt('1f')).to.be.revertedWithCustomError(this.mock, 'StringsInvalidChar'); + await expect(this.mock.$parseInt('1.0')).to.be.revertedWithCustomError(this.mock, 'StringsInvalidChar'); + await expect(this.mock.$parseInt('1 000')).to.be.revertedWithCustomError(this.mock, 'StringsInvalidChar'); + expect(await this.mock.$tryParseInt('0x1')).to.deep.equal([false, 0n]); + expect(await this.mock.$tryParseInt('1f')).to.deep.equal([false, 0n]); + expect(await this.mock.$tryParseInt('1.0')).to.deep.equal([false, 0n]); + expect(await this.mock.$tryParseInt('1 000')).to.deep.equal([false, 0n]); + }); + + it('parseHexUint overflow', async function () { + await expect(this.mock.$parseHexUint((ethers.MaxUint256 + 1n).toString(16))).to.be.revertedWithPanic( + PANIC_CODES.ARITHMETIC_OVERFLOW, + ); + await expect(this.mock.$tryParseHexUint((ethers.MaxUint256 + 1n).toString(16))).to.be.revertedWithPanic( + PANIC_CODES.ARITHMETIC_OVERFLOW, + ); + }); + + it('parseHexUint invalid character', async function () { + await expect(this.mock.$parseHexUint('0123456789abcdefg')).to.be.revertedWithCustomError( + this.mock, + 'StringsInvalidChar', + ); + await expect(this.mock.$parseHexUint('-1')).to.be.revertedWithCustomError(this.mock, 'StringsInvalidChar'); + await expect(this.mock.$parseHexUint('-f')).to.be.revertedWithCustomError(this.mock, 'StringsInvalidChar'); + await expect(this.mock.$parseHexUint('-0xf')).to.be.revertedWithCustomError(this.mock, 'StringsInvalidChar'); + await expect(this.mock.$parseHexUint('1.0')).to.be.revertedWithCustomError(this.mock, 'StringsInvalidChar'); + await expect(this.mock.$parseHexUint('1 000')).to.be.revertedWithCustomError(this.mock, 'StringsInvalidChar'); + expect(await this.mock.$tryParseHexUint('0123456789abcdefg')).to.deep.equal([false, 0n]); + expect(await this.mock.$tryParseHexUint('-1')).to.deep.equal([false, 0n]); + expect(await this.mock.$tryParseHexUint('-f')).to.deep.equal([false, 0n]); + expect(await this.mock.$tryParseHexUint('-0xf')).to.deep.equal([false, 0n]); + expect(await this.mock.$tryParseHexUint('1.0')).to.deep.equal([false, 0n]); + expect(await this.mock.$tryParseHexUint('1 000')).to.deep.equal([false, 0n]); + }); + + it('parseAddress invalid format', async function () { + for (const addr of [ + '0x736a507fB2881d6bB62dcA54673CF5295dC07833', // valid + '0x736a507fB2881d6-B62dcA54673CF5295dC07833', // invalid char + '0x0736a507fB2881d6bB62dcA54673CF5295dC07833', // tooLong + '0x36a507fB2881d6bB62dcA54673CF5295dC07833', // tooShort + '736a507fB2881d6bB62dcA54673CF5295dC07833', // missingPrefix - supported + ]) { + if (ethers.isAddress(addr)) { + expect(await this.mock.$parseAddress(addr)).to.equal(ethers.getAddress(addr)); + expect(await this.mock.$tryParseAddress(addr)).to.deep.equal([true, ethers.getAddress(addr)]); + } else { + await expect(this.mock.$parseAddress(addr)).to.be.revertedWithCustomError( + this.mock, + 'StringsInvalidAddressFormat', + ); + expect(await this.mock.$tryParseAddress(addr)).to.deep.equal([false, ethers.ZeroAddress]); + } + } + }); + }); }); From fe6249ec2c2dcdbb05e2b6a0d0d3e229f6ffbb37 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Mon, 14 Oct 2024 22:41:08 +0200 Subject: [PATCH 29/84] Bytes library and CAIP2/CAIP10 helpers (#5252) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: cairo Co-authored-by: Ernesto García Co-authored-by: Arr00 <13561405+arr00@users.noreply.github.com> --- .changeset/healthy-books-shout.md | 5 ++ .changeset/proud-planes-arrive.md | 5 ++ contracts/mocks/Stateless.sol | 3 + contracts/utils/Bytes.sol | 115 ++++++++++++++++++++++++++++++ contracts/utils/CAIP10.sol | 49 +++++++++++++ contracts/utils/CAIP2.sol | 47 ++++++++++++ contracts/utils/README.adoc | 2 + test/helpers/chains.js | 109 ++++++++++++++++++++++++++++ test/utils/Bytes.test.js | 88 +++++++++++++++++++++++ test/utils/CAIP.test.js | 53 ++++++++++++++ 10 files changed, 476 insertions(+) create mode 100644 .changeset/healthy-books-shout.md create mode 100644 .changeset/proud-planes-arrive.md create mode 100644 contracts/utils/Bytes.sol create mode 100644 contracts/utils/CAIP10.sol create mode 100644 contracts/utils/CAIP2.sol create mode 100644 test/helpers/chains.js create mode 100644 test/utils/Bytes.test.js create mode 100644 test/utils/CAIP.test.js diff --git a/.changeset/healthy-books-shout.md b/.changeset/healthy-books-shout.md new file mode 100644 index 00000000000..274e7a48868 --- /dev/null +++ b/.changeset/healthy-books-shout.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': minor +--- + +`CAIP2` and `CAIP10`: Add libraries for formatting and parsing CAIP-2 and CAIP-10 identifiers. diff --git a/.changeset/proud-planes-arrive.md b/.changeset/proud-planes-arrive.md new file mode 100644 index 00000000000..6408976414d --- /dev/null +++ b/.changeset/proud-planes-arrive.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': minor +--- + +`Bytes`: Add a library of common operation that operate on `bytes` objects. diff --git a/contracts/mocks/Stateless.sol b/contracts/mocks/Stateless.sol index 846c77d98e8..98e7eaf7443 100644 --- a/contracts/mocks/Stateless.sol +++ b/contracts/mocks/Stateless.sol @@ -9,6 +9,9 @@ import {Arrays} from "../utils/Arrays.sol"; import {AuthorityUtils} from "../access/manager/AuthorityUtils.sol"; import {Base64} from "../utils/Base64.sol"; import {BitMaps} from "../utils/structs/BitMaps.sol"; +import {Bytes} from "../utils/Bytes.sol"; +import {CAIP2} from "../utils/CAIP2.sol"; +import {CAIP10} from "../utils/CAIP10.sol"; import {Checkpoints} from "../utils/structs/Checkpoints.sol"; import {CircularBuffer} from "../utils/structs/CircularBuffer.sol"; import {Clones} from "../proxy/Clones.sol"; diff --git a/contracts/utils/Bytes.sol b/contracts/utils/Bytes.sol new file mode 100644 index 00000000000..84e5a3ed51f --- /dev/null +++ b/contracts/utils/Bytes.sol @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {Math} from "./math/Math.sol"; + +/** + * @dev Bytes operations. + */ +library Bytes { + /** + * @dev Forward search for `s` in `buffer` + * * If `s` is present in the buffer, returns the index of the first instance + * * If `s` is not present in the buffer, returns type(uint256).max + * + * NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf[Javascript's `Array.indexOf`] + */ + function indexOf(bytes memory buffer, bytes1 s) internal pure returns (uint256) { + return indexOf(buffer, s, 0); + } + + /** + * @dev Forward search for `s` in `buffer` starting at position `pos` + * * If `s` is present in the buffer (at or after `pos`), returns the index of the next instance + * * If `s` is not present in the buffer (at or after `pos`), returns type(uint256).max + * + * NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf[Javascript's `Array.indexOf`] + */ + function indexOf(bytes memory buffer, bytes1 s, uint256 pos) internal pure returns (uint256) { + unchecked { + uint256 length = buffer.length; + for (uint256 i = pos; i < length; ++i) { + if (bytes1(_unsafeReadBytesOffset(buffer, i)) == s) { + return i; + } + } + return type(uint256).max; + } + } + + /** + * @dev Backward search for `s` in `buffer` + * * If `s` is present in the buffer, returns the index of the last instance + * * If `s` is not present in the buffer, returns type(uint256).max + * + * NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/lastIndexOf[Javascript's `Array.lastIndexOf`] + */ + function lastIndexOf(bytes memory buffer, bytes1 s) internal pure returns (uint256) { + return lastIndexOf(buffer, s, type(uint256).max); + } + + /** + * @dev Backward search for `s` in `buffer` starting at position `pos` + * * If `s` is present in the buffer (at or before `pos`), returns the index of the previous instance + * * If `s` is not present in the buffer (at or before `pos`), returns type(uint256).max + * + * NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/lastIndexOf[Javascript's `Array.lastIndexOf`] + */ + function lastIndexOf(bytes memory buffer, bytes1 s, uint256 pos) internal pure returns (uint256) { + unchecked { + uint256 length = buffer.length; + // NOTE here we cannot do `i = Math.min(pos + 1, length)` because `pos + 1` could overflow + for (uint256 i = Math.min(pos, length - 1) + 1; i > 0; --i) { + if (bytes1(_unsafeReadBytesOffset(buffer, i - 1)) == s) { + return i - 1; + } + } + return type(uint256).max; + } + } + + /** + * @dev Copies the content of `buffer`, from `start` (included) to the end of `buffer` into a new bytes object in + * memory. + * + * NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice[Javascript's `Array.slice`] + */ + function slice(bytes memory buffer, uint256 start) internal pure returns (bytes memory) { + return slice(buffer, start, buffer.length); + } + + /** + * @dev Copies the content of `buffer`, from `start` (included) to `end` (excluded) into a new bytes object in + * memory. + * + * NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice[Javascript's `Array.slice`] + */ + function slice(bytes memory buffer, uint256 start, uint256 end) internal pure returns (bytes memory) { + // sanitize + uint256 length = buffer.length; + end = Math.min(end, length); + start = Math.min(start, end); + + // allocate and copy + bytes memory result = new bytes(end - start); + assembly ("memory-safe") { + mcopy(add(result, 0x20), add(buffer, add(start, 0x20)), sub(end, start)) + } + + return result; + } + + /** + * @dev Reads a bytes32 from a bytes array without bounds checking. + * + * NOTE: making this function internal would mean it could be used with memory unsafe offset, and marking the + * assembly block as such would prevent some optimizations. + */ + function _unsafeReadBytesOffset(bytes memory buffer, uint256 offset) private pure returns (bytes32 value) { + // This is not memory safe in the general case, but all calls to this private function are within bounds. + assembly ("memory-safe") { + value := mload(add(buffer, add(0x20, offset))) + } + } +} diff --git a/contracts/utils/CAIP10.sol b/contracts/utils/CAIP10.sol new file mode 100644 index 00000000000..e9ed17305b6 --- /dev/null +++ b/contracts/utils/CAIP10.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {SafeCast} from "./math/SafeCast.sol"; +import {Bytes} from "./Bytes.sol"; +import {CAIP2} from "./CAIP2.sol"; +import {Strings} from "./Strings.sol"; + +/** + * @dev Helper library to format and parse CAIP-10 identifiers + * + * https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-10.md[CAIP-10] defines account identifiers as: + * account_id: chain_id + ":" + account_address + * chain_id: [-a-z0-9]{3,8}:[-_a-zA-Z0-9]{1,32} (See {CAIP2}) + * account_address: [-.%a-zA-Z0-9]{1,128} + */ +library CAIP10 { + using SafeCast for uint256; + using Strings for address; + using Bytes for bytes; + + /// @dev Return the CAIP-10 identifier for an account on the current (local) chain. + function local(address account) internal view returns (string memory) { + return format(CAIP2.local(), account.toChecksumHexString()); + } + + /** + * @dev Return the CAIP-10 identifier for a given caip2 chain and account. + * + * NOTE: This function does not verify that the inputs are properly formatted. + */ + function format(string memory caip2, string memory account) internal pure returns (string memory) { + return string.concat(caip2, ":", account); + } + + /** + * @dev Parse a CAIP-10 identifier into its components. + * + * NOTE: This function does not verify that the CAIP-10 input is properly formatted. The `caip2` return can be + * parsed using the {CAIP2} library. + */ + function parse(string memory caip10) internal pure returns (string memory caip2, string memory account) { + bytes memory buffer = bytes(caip10); + + uint256 pos = buffer.lastIndexOf(":"); + return (string(buffer.slice(0, pos)), string(buffer.slice(pos + 1))); + } +} diff --git a/contracts/utils/CAIP2.sol b/contracts/utils/CAIP2.sol new file mode 100644 index 00000000000..13a98f58a46 --- /dev/null +++ b/contracts/utils/CAIP2.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {SafeCast} from "./math/SafeCast.sol"; +import {Bytes} from "./Bytes.sol"; +import {Strings} from "./Strings.sol"; + +/** + * @dev Helper library to format and parse CAIP-2 identifiers + * + * https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-2.md[CAIP-2] defines chain identifiers as: + * chain_id: namespace + ":" + reference + * namespace: [-a-z0-9]{3,8} + * reference: [-_a-zA-Z0-9]{1,32} + */ +library CAIP2 { + using SafeCast for uint256; + using Strings for uint256; + using Bytes for bytes; + + /// @dev Return the CAIP-2 identifier for the current (local) chain. + function local() internal view returns (string memory) { + return format("eip155", block.chainid.toString()); + } + + /** + * @dev Return the CAIP-2 identifier for a given namespace and reference. + * + * NOTE: This function does not verify that the inputs are properly formatted. + */ + function format(string memory namespace, string memory ref) internal pure returns (string memory) { + return string.concat(namespace, ":", ref); + } + + /** + * @dev Parse a CAIP-2 identifier into its components. + * + * NOTE: This function does not verify that the CAIP-2 input is properly formatted. + */ + function parse(string memory caip2) internal pure returns (string memory namespace, string memory ref) { + bytes memory buffer = bytes(caip2); + + uint256 pos = buffer.indexOf(":"); + return (string(buffer.slice(0, pos)), string(buffer.slice(pos + 1))); + } +} diff --git a/contracts/utils/README.adoc b/contracts/utils/README.adoc index 245c89c0486..eeef84aae7c 100644 --- a/contracts/utils/README.adoc +++ b/contracts/utils/README.adoc @@ -31,6 +31,7 @@ Miscellaneous contracts and libraries containing utility functions you can use t * {Address}: Collection of functions for overloading Solidity's https://docs.soliditylang.org/en/latest/types.html#address[`address`] type. * {Arrays}: Collection of functions that operate on https://docs.soliditylang.org/en/latest/types.html#arrays[`arrays`]. * {Base64}: On-chain base64 and base64URL encoding according to https://datatracker.ietf.org/doc/html/rfc4648[RFC-4648]. + * {Bytes}: Common operations on bytes objects. * {Strings}: Common operations for strings formatting. * {ShortString}: Library to encode (and decode) short strings into (or from) a single bytes32 slot for optimizing costs. Short strings are limited to 31 characters. * {SlotDerivation}: Methods for deriving storage slot from ERC-7201 namespaces as well as from constructions such as mapping and arrays. @@ -41,6 +42,7 @@ Miscellaneous contracts and libraries containing utility functions you can use t * {Packing}: A library for packing and unpacking multiple values into bytes32 * {Panic}: A library to revert with https://docs.soliditylang.org/en/v0.8.20/control-structures.html#panic-via-assert-and-error-via-require[Solidity panic codes]. * {Comparators}: A library that contains comparator functions to use with with the {Heap} library. + * {CAIP2}, {CAIP10}: Libraries for formatting and parsing CAIP-2 and CAIP-10 identifiers. [NOTE] ==== diff --git a/test/helpers/chains.js b/test/helpers/chains.js new file mode 100644 index 00000000000..3711a81258e --- /dev/null +++ b/test/helpers/chains.js @@ -0,0 +1,109 @@ +// NOTE: this file defines some examples of CAIP-2 and CAIP-10 identifiers. +// The following listing does not pretend to be exhaustive or even accurate. It SHOULD NOT be used in production. + +const { ethers } = require('hardhat'); +const { mapValues } = require('./iterate'); + +// EVM (https://axelarscan.io/resources/chains?type=evm) +const ethereum = { + Ethereum: '1', + optimism: '10', + binance: '56', + Polygon: '137', + Fantom: '250', + fraxtal: '252', + filecoin: '314', + Moonbeam: '1284', + centrifuge: '2031', + kava: '2222', + mantle: '5000', + base: '8453', + immutable: '13371', + arbitrum: '42161', + celo: '42220', + Avalanche: '43114', + linea: '59144', + blast: '81457', + scroll: '534352', + aurora: '1313161554', +}; + +// Cosmos (https://axelarscan.io/resources/chains?type=cosmos) +const cosmos = { + Axelarnet: 'axelar-dojo-1', + osmosis: 'osmosis-1', + cosmoshub: 'cosmoshub-4', + juno: 'juno-1', + 'e-money': 'emoney-3', + injective: 'injective-1', + crescent: 'crescent-1', + kujira: 'kaiyo-1', + 'secret-snip': 'secret-4', + secret: 'secret-4', + sei: 'pacific-1', + stargaze: 'stargaze-1', + assetmantle: 'mantle-1', + fetch: 'fetchhub-4', + ki: 'kichain-2', + evmos: 'evmos_9001-2', + aura: 'xstaxy-1', + comdex: 'comdex-1', + persistence: 'core-1', + regen: 'regen-1', + umee: 'umee-1', + agoric: 'agoric-3', + xpla: 'dimension_37-1', + acre: 'acre_9052-1', + stride: 'stride-1', + carbon: 'carbon-1', + sommelier: 'sommelier-3', + neutron: 'neutron-1', + rebus: 'reb_1111-1', + archway: 'archway-1', + provenance: 'pio-mainnet-1', + ixo: 'ixo-5', + migaloo: 'migaloo-1', + teritori: 'teritori-1', + haqq: 'haqq_11235-1', + celestia: 'celestia', + ojo: 'agamotto', + chihuahua: 'chihuahua-1', + saga: 'ssc-1', + dymension: 'dymension_1100-1', + fxcore: 'fxcore', + c4e: 'perun-1', + bitsong: 'bitsong-2b', + nolus: 'pirin-1', + lava: 'lava-mainnet-1', + 'terra-2': 'phoenix-1', + terra: 'columbus-5', +}; + +const makeCAIP = ({ namespace, reference, account }) => ({ + namespace, + reference, + account, + caip2: `${namespace}:${reference}`, + caip10: `${namespace}:${reference}:${account}`, + toCaip10: other => `${namespace}:${reference}:${ethers.getAddress(other.target ?? other.address ?? other)}`, +}); + +module.exports = { + CHAINS: mapValues( + Object.assign( + mapValues(ethereum, reference => ({ + namespace: 'eip155', + reference, + account: ethers.Wallet.createRandom().address, + })), + mapValues(cosmos, reference => ({ + namespace: 'cosmos', + reference, + account: ethers.encodeBase58(ethers.randomBytes(32)), + })), + ), + makeCAIP, + ), + getLocalCAIP: account => + ethers.provider.getNetwork().then(({ chainId }) => makeCAIP({ namespace: 'eip155', reference: chainId, account })), +}; diff --git a/test/utils/Bytes.test.js b/test/utils/Bytes.test.js new file mode 100644 index 00000000000..52a1ae95e77 --- /dev/null +++ b/test/utils/Bytes.test.js @@ -0,0 +1,88 @@ +const { ethers } = require('hardhat'); +const { expect } = require('chai'); +const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); + +async function fixture() { + const mock = await ethers.deployContract('$Bytes'); + return { mock }; +} + +const lorem = ethers.toUtf8Bytes( + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', +); +const present = lorem.at(1); +const absent = 255; + +describe('Bytes', function () { + before(async function () { + Object.assign(this, await loadFixture(fixture)); + }); + + describe('indexOf', function () { + it('first', async function () { + expect(await this.mock.$indexOf(lorem, ethers.toBeHex(present))).to.equal(lorem.indexOf(present)); + }); + + it('from index', async function () { + for (const start in Array(lorem.length + 10).fill()) { + const index = lorem.indexOf(present, start); + const result = index === -1 ? ethers.MaxUint256 : index; + expect(await this.mock.$indexOf(lorem, ethers.toBeHex(present), ethers.Typed.uint256(start))).to.equal(result); + } + }); + + it('absent', async function () { + expect(await this.mock.$indexOf(lorem, ethers.toBeHex(absent))).to.equal(ethers.MaxUint256); + }); + }); + + describe('lastIndexOf', function () { + it('first', async function () { + expect(await this.mock.$lastIndexOf(lorem, ethers.toBeHex(present))).to.equal(lorem.lastIndexOf(present)); + }); + + it('from index', async function () { + for (const start in Array(lorem.length + 10).fill()) { + const index = lorem.lastIndexOf(present, start); + const result = index === -1 ? ethers.MaxUint256 : index; + expect(await this.mock.$lastIndexOf(lorem, ethers.toBeHex(present), ethers.Typed.uint256(start))).to.equal( + result, + ); + } + }); + + it('absent', async function () { + expect(await this.mock.$lastIndexOf(lorem, ethers.toBeHex(absent))).to.equal(ethers.MaxUint256); + }); + }); + + describe('slice', function () { + describe('slice(bytes, uint256)', function () { + for (const [descr, start] of Object.entries({ + 'start = 0': 0, + 'start within bound': 10, + 'start out of bound': 1000, + })) { + it(descr, async function () { + const result = ethers.hexlify(lorem.slice(start)); + expect(await this.mock.$slice(lorem, start)).to.equal(result); + }); + } + }); + + describe('slice(bytes, uint256, uint256)', function () { + for (const [descr, [start, end]] of Object.entries({ + 'start = 0': [0, 42], + 'start and end within bound': [17, 42], + 'end out of bound': [42, 1000], + 'start = end': [17, 17], + 'start > end': [42, 17], + })) { + it(descr, async function () { + const result = ethers.hexlify(lorem.slice(start, end)); + expect(await this.mock.$slice(lorem, start, ethers.Typed.uint256(end))).to.equal(result); + }); + } + }); + }); +}); diff --git a/test/utils/CAIP.test.js b/test/utils/CAIP.test.js new file mode 100644 index 00000000000..cd5995cade0 --- /dev/null +++ b/test/utils/CAIP.test.js @@ -0,0 +1,53 @@ +const { ethers } = require('hardhat'); +const { expect } = require('chai'); +const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); + +const { CHAINS, getLocalCAIP } = require('../helpers/chains'); + +async function fixture() { + const caip2 = await ethers.deployContract('$CAIP2'); + const caip10 = await ethers.deployContract('$CAIP10'); + return { caip2, caip10 }; +} + +describe('CAIP utilities', function () { + beforeEach(async function () { + Object.assign(this, await loadFixture(fixture)); + }); + + describe('CAIP-2', function () { + it('local()', async function () { + const { caip2 } = await getLocalCAIP(); + expect(await this.caip2.$local()).to.equal(caip2); + }); + + for (const { namespace, reference, caip2 } of Object.values(CHAINS)) + it(`format(${namespace}, ${reference})`, async function () { + expect(await this.caip2.$format(namespace, reference)).to.equal(caip2); + }); + + for (const { namespace, reference, caip2 } of Object.values(CHAINS)) + it(`parse(${caip2})`, async function () { + expect(await this.caip2.$parse(caip2)).to.deep.equal([namespace, reference]); + }); + }); + + describe('CAIP-10', function () { + const { address: account } = ethers.Wallet.createRandom(); + + it(`local(${account})`, async function () { + const { caip10 } = await getLocalCAIP(account); + expect(await this.caip10.$local(ethers.Typed.address(account))).to.equal(caip10); + }); + + for (const { account, caip2, caip10 } of Object.values(CHAINS)) + it(`format(${caip2}, ${account})`, async function () { + expect(await this.caip10.$format(ethers.Typed.string(caip2), ethers.Typed.string(account))).to.equal(caip10); + }); + + for (const { account, caip2, caip10 } of Object.values(CHAINS)) + it(`parse(${caip10})`, async function () { + expect(await this.caip10.$parse(caip10)).to.deep.equal([caip2, account]); + }); + }); +}); From 3291252c866ad698f6a55ec660259e49a67eb3d0 Mon Sep 17 00:00:00 2001 From: cairo Date: Thu, 17 Oct 2024 04:33:22 -0700 Subject: [PATCH 30/84] Document risk of `SafeERC20` and `ERC-7674` (#5262) --- .changeset/yellow-tables-sell.md | 5 +++++ contracts/token/ERC20/utils/SafeERC20.sol | 14 ++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 .changeset/yellow-tables-sell.md diff --git a/.changeset/yellow-tables-sell.md b/.changeset/yellow-tables-sell.md new file mode 100644 index 00000000000..f8cdc8d306b --- /dev/null +++ b/.changeset/yellow-tables-sell.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': minor +--- + +`SafeERC20`: Document risks of `safeIncreaseAllowance` and `safeDecreaseAllowance` when associated with ERC-7674. diff --git a/contracts/token/ERC20/utils/SafeERC20.sol b/contracts/token/ERC20/utils/SafeERC20.sol index ed41fb042c9..19c2f3e19e8 100644 --- a/contracts/token/ERC20/utils/SafeERC20.sol +++ b/contracts/token/ERC20/utils/SafeERC20.sol @@ -46,6 +46,11 @@ library SafeERC20 { /** * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value, * non-reverting calls are assumed to be successful. + * + * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client" + * smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using + * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract + * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior. */ function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal { uint256 oldAllowance = token.allowance(address(this), spender); @@ -55,6 +60,11 @@ library SafeERC20 { /** * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no * value, non-reverting calls are assumed to be successful. + * + * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client" + * smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using + * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract + * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior. */ function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal { unchecked { @@ -70,6 +80,10 @@ library SafeERC20 { * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value, * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval * to be set to zero before setting it to a non-zero value, such as USDT. + * + * NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function + * only sets the "standard" allowance. Any temporary allowance will remain active, in addition to the value being + * set here. */ function forceApprove(IERC20 token, address spender, uint256 value) internal { bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value)); From 0034c302241c4b1a1685272d4df42ca5d64b8c34 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 17 Oct 2024 17:41:15 +0200 Subject: [PATCH 31/84] Merge release-v5.1 branch (#5266) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Hadrien Croubois Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Hadrien Croubois Co-authored-by: cairo Co-authored-by: Ernesto García Co-authored-by: sudo rm -rf --no-preserve-root / Co-authored-by: danilo neves cruz Co-authored-by: omahs <73983677+omahs@users.noreply.github.com> Co-authored-by: skyge <1506186404li@gmail.com> Co-authored-by: PurrProof <149718167+PurrProof@users.noreply.github.com> Co-authored-by: Eric Lau Co-authored-by: plooten Co-authored-by: github-actions[bot] Co-authored-by: Md Zartaj Afser <91191500+Zartaj0@users.noreply.github.com> --- .changeset/chilled-walls-develop.md | 5 -- .changeset/chilly-humans-warn.md | 5 -- .changeset/cold-cheetahs-check.md | 5 -- .changeset/cool-mangos-compare.md | 5 -- .changeset/curvy-crabs-repeat.md | 5 -- .changeset/dirty-cobras-smile.md | 5 -- .changeset/eight-eyes-burn.md | 5 -- .changeset/eleven-planets-relax.md | 5 -- .changeset/flat-turtles-repeat.md | 5 -- .changeset/fluffy-buses-jump.md | 5 -- .changeset/fluffy-steaks-exist.md | 5 -- .changeset/forty-dodos-visit.md | 5 -- .changeset/friendly-nails-push.md | 5 -- .changeset/gentle-bulldogs-turn.md | 5 -- .changeset/gorgeous-badgers-vanish.md | 5 -- .changeset/great-pianos-work.md | 5 -- .changeset/heavy-baboons-give.md | 5 -- .changeset/kind-planets-cough.md | 5 -- .changeset/light-news-listen.md | 5 -- .changeset/lucky-crews-eat.md | 5 -- .changeset/nervous-eyes-teach.md | 5 -- .changeset/nervous-pans-grow.md | 5 -- .changeset/nice-paws-pull.md | 5 -- .changeset/odd-files-protect.md | 5 -- .changeset/odd-lobsters-wash.md | 5 -- .changeset/poor-chefs-cheat.md | 5 -- .changeset/serious-carrots-provide.md | 5 -- .changeset/shiny-poets-whisper.md | 5 -- .changeset/silver-swans-promise.md | 5 -- .changeset/smart-bugs-switch.md | 5 -- .changeset/spotty-falcons-explain.md | 5 -- .changeset/spotty-queens-own.md | 5 -- .changeset/strong-singers-talk.md | 5 -- .changeset/thick-pumpkins-report.md | 5 -- .changeset/thin-walls-drop.md | 5 -- .changeset/twenty-feet-grin.md | 5 -- .changeset/violet-moons-tell.md | 5 -- .changeset/warm-sheep-cover.md | 5 -- .changeset/wise-bobcats-speak.md | 5 -- .changeset/witty-chicken-smile.md | 5 -- .changeset/yellow-deers-walk.md | 5 -- .changeset/yellow-moles-hammer.md | 5 -- CHANGELOG.md | 81 ++++++++++++++++++- contracts/access/IAccessControl.sol | 2 +- contracts/access/Ownable2Step.sol | 2 +- .../extensions/AccessControlEnumerable.sol | 2 +- .../IAccessControlDefaultAdminRules.sol | 2 +- .../extensions/IAccessControlEnumerable.sol | 2 +- contracts/access/manager/AccessManaged.sol | 2 +- contracts/access/manager/AccessManager.sol | 2 +- contracts/access/manager/IAccessManager.sol | 2 +- contracts/finance/VestingWallet.sol | 2 +- contracts/finance/VestingWalletCliff.sol | 1 + contracts/governance/Governor.sol | 2 +- contracts/governance/IGovernor.sol | 2 +- .../extensions/GovernorCountingFractional.sol | 1 + .../extensions/GovernorCountingSimple.sol | 2 +- .../governance/extensions/GovernorStorage.sol | 2 +- .../extensions/GovernorTimelockAccess.sol | 2 +- .../extensions/GovernorTimelockCompound.sol | 2 +- .../extensions/GovernorTimelockControl.sol | 2 +- .../governance/extensions/GovernorVotes.sol | 2 +- contracts/governance/utils/Votes.sol | 2 +- contracts/interfaces/IERC1271.sol | 2 +- contracts/interfaces/IERC1363.sol | 2 +- contracts/interfaces/IERC1363Receiver.sol | 2 +- contracts/interfaces/IERC1363Spender.sol | 2 +- contracts/interfaces/IERC1820Implementer.sol | 2 +- contracts/interfaces/IERC1820Registry.sol | 2 +- contracts/interfaces/IERC2981.sol | 2 +- .../interfaces/IERC3156FlashBorrower.sol | 2 +- contracts/interfaces/IERC3156FlashLender.sol | 2 +- contracts/interfaces/IERC4626.sol | 2 +- contracts/interfaces/IERC4906.sol | 2 +- contracts/interfaces/IERC777.sol | 2 +- contracts/interfaces/IERC777Recipient.sol | 2 +- contracts/interfaces/IERC777Sender.sol | 2 +- contracts/interfaces/draft-IERC1822.sol | 2 +- contracts/interfaces/draft-IERC6093.sol | 2 +- contracts/interfaces/draft-IERC7674.sol | 1 + contracts/metatx/ERC2771Context.sol | 2 +- contracts/metatx/ERC2771Forwarder.sol | 2 +- contracts/package.json | 2 +- contracts/proxy/Clones.sol | 2 +- contracts/proxy/ERC1967/ERC1967Proxy.sol | 2 +- contracts/proxy/ERC1967/ERC1967Utils.sol | 2 +- contracts/proxy/beacon/BeaconProxy.sol | 2 +- contracts/proxy/transparent/ProxyAdmin.sol | 2 +- .../TransparentUpgradeableProxy.sol | 2 +- contracts/proxy/utils/UUPSUpgradeable.sol | 2 +- contracts/token/ERC1155/ERC1155.sol | 2 +- contracts/token/ERC1155/IERC1155.sol | 2 +- contracts/token/ERC1155/IERC1155Receiver.sol | 2 +- .../ERC1155/extensions/ERC1155Pausable.sol | 2 +- .../ERC1155/extensions/ERC1155Supply.sol | 2 +- .../ERC1155/extensions/ERC1155URIStorage.sol | 2 +- .../extensions/IERC1155MetadataURI.sol | 2 +- .../token/ERC1155/utils/ERC1155Holder.sol | 2 +- .../token/ERC1155/utils/ERC1155Utils.sol | 1 + contracts/token/ERC20/ERC20.sol | 2 +- contracts/token/ERC20/IERC20.sol | 2 +- contracts/token/ERC20/extensions/ERC1363.sol | 1 + .../token/ERC20/extensions/ERC20FlashMint.sol | 2 +- .../token/ERC20/extensions/ERC20Pausable.sol | 2 +- .../token/ERC20/extensions/ERC20Permit.sol | 2 +- .../token/ERC20/extensions/ERC20Votes.sol | 2 +- .../token/ERC20/extensions/ERC20Wrapper.sol | 2 +- contracts/token/ERC20/extensions/ERC4626.sol | 2 +- .../token/ERC20/extensions/IERC20Metadata.sol | 2 +- .../token/ERC20/extensions/IERC20Permit.sol | 2 +- .../draft-ERC20TemporaryApproval.sol | 1 + contracts/token/ERC20/utils/ERC1363Utils.sol | 1 + contracts/token/ERC20/utils/SafeERC20.sol | 2 +- contracts/token/ERC721/ERC721.sol | 2 +- contracts/token/ERC721/IERC721.sol | 2 +- contracts/token/ERC721/IERC721Receiver.sol | 2 +- .../ERC721/extensions/ERC721Burnable.sol | 2 +- .../ERC721/extensions/ERC721Consecutive.sol | 2 +- .../ERC721/extensions/ERC721Enumerable.sol | 2 +- .../ERC721/extensions/ERC721Pausable.sol | 2 +- .../token/ERC721/extensions/ERC721Royalty.sol | 2 +- .../ERC721/extensions/ERC721URIStorage.sol | 2 +- .../token/ERC721/extensions/ERC721Votes.sol | 2 +- .../token/ERC721/extensions/ERC721Wrapper.sol | 2 +- contracts/token/ERC721/utils/ERC721Utils.sol | 1 + contracts/token/common/ERC2981.sol | 2 +- contracts/utils/Address.sol | 2 +- contracts/utils/Arrays.sol | 2 +- contracts/utils/Base64.sol | 2 +- contracts/utils/Comparators.sol | 1 + contracts/utils/Create2.sol | 2 +- contracts/utils/Errors.sol | 1 + contracts/utils/Packing.sol | 1 + contracts/utils/Panic.sol | 1 + contracts/utils/ReentrancyGuard.sol | 2 +- contracts/utils/ReentrancyGuardTransient.sol | 1 + contracts/utils/ShortStrings.sol | 2 +- contracts/utils/SlotDerivation.sol | 1 + contracts/utils/StorageSlot.sol | 2 +- contracts/utils/Strings.sol | 2 +- contracts/utils/TransientSlot.sol | 1 + contracts/utils/cryptography/ECDSA.sol | 2 +- contracts/utils/cryptography/EIP712.sol | 2 +- contracts/utils/cryptography/Hashes.sol | 1 + contracts/utils/cryptography/MerkleProof.sol | 2 +- .../utils/cryptography/MessageHashUtils.sol | 2 +- contracts/utils/cryptography/P256.sol | 1 + contracts/utils/cryptography/RSA.sol | 1 + .../utils/cryptography/SignatureChecker.sol | 2 +- contracts/utils/introspection/ERC165.sol | 2 +- .../utils/introspection/ERC165Checker.sol | 2 +- contracts/utils/introspection/IERC165.sol | 2 +- contracts/utils/math/Math.sol | 2 +- contracts/utils/math/SafeCast.sol | 2 +- contracts/utils/math/SignedMath.sol | 2 +- contracts/utils/structs/Checkpoints.sol | 2 +- contracts/utils/structs/CircularBuffer.sol | 1 + contracts/utils/structs/DoubleEndedQueue.sol | 2 +- contracts/utils/structs/EnumerableMap.sol | 2 +- contracts/utils/structs/EnumerableSet.sol | 2 +- contracts/utils/structs/Heap.sol | 1 + contracts/utils/structs/MerkleTree.sol | 1 + contracts/utils/types/Time.sol | 2 +- package.json | 2 +- 164 files changed, 201 insertions(+), 311 deletions(-) delete mode 100644 .changeset/chilled-walls-develop.md delete mode 100644 .changeset/chilly-humans-warn.md delete mode 100644 .changeset/cold-cheetahs-check.md delete mode 100644 .changeset/cool-mangos-compare.md delete mode 100644 .changeset/curvy-crabs-repeat.md delete mode 100644 .changeset/dirty-cobras-smile.md delete mode 100644 .changeset/eight-eyes-burn.md delete mode 100644 .changeset/eleven-planets-relax.md delete mode 100644 .changeset/flat-turtles-repeat.md delete mode 100644 .changeset/fluffy-buses-jump.md delete mode 100644 .changeset/fluffy-steaks-exist.md delete mode 100644 .changeset/forty-dodos-visit.md delete mode 100644 .changeset/friendly-nails-push.md delete mode 100644 .changeset/gentle-bulldogs-turn.md delete mode 100644 .changeset/gorgeous-badgers-vanish.md delete mode 100644 .changeset/great-pianos-work.md delete mode 100644 .changeset/heavy-baboons-give.md delete mode 100644 .changeset/kind-planets-cough.md delete mode 100644 .changeset/light-news-listen.md delete mode 100644 .changeset/lucky-crews-eat.md delete mode 100644 .changeset/nervous-eyes-teach.md delete mode 100644 .changeset/nervous-pans-grow.md delete mode 100644 .changeset/nice-paws-pull.md delete mode 100644 .changeset/odd-files-protect.md delete mode 100644 .changeset/odd-lobsters-wash.md delete mode 100644 .changeset/poor-chefs-cheat.md delete mode 100644 .changeset/serious-carrots-provide.md delete mode 100644 .changeset/shiny-poets-whisper.md delete mode 100644 .changeset/silver-swans-promise.md delete mode 100644 .changeset/smart-bugs-switch.md delete mode 100644 .changeset/spotty-falcons-explain.md delete mode 100644 .changeset/spotty-queens-own.md delete mode 100644 .changeset/strong-singers-talk.md delete mode 100644 .changeset/thick-pumpkins-report.md delete mode 100644 .changeset/thin-walls-drop.md delete mode 100644 .changeset/twenty-feet-grin.md delete mode 100644 .changeset/violet-moons-tell.md delete mode 100644 .changeset/warm-sheep-cover.md delete mode 100644 .changeset/wise-bobcats-speak.md delete mode 100644 .changeset/witty-chicken-smile.md delete mode 100644 .changeset/yellow-deers-walk.md delete mode 100644 .changeset/yellow-moles-hammer.md diff --git a/.changeset/chilled-walls-develop.md b/.changeset/chilled-walls-develop.md deleted file mode 100644 index 4108feb612d..00000000000 --- a/.changeset/chilled-walls-develop.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`Clones`: Add version of `clone` and `cloneDeterministic` that support sending value at creation. diff --git a/.changeset/chilly-humans-warn.md b/.changeset/chilly-humans-warn.md deleted file mode 100644 index 1301dfec416..00000000000 --- a/.changeset/chilly-humans-warn.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': patch ---- - -`ProxyAdmin`: Fixed documentation for `UPGRADE_INTERFACE_VERSION` getter. diff --git a/.changeset/cold-cheetahs-check.md b/.changeset/cold-cheetahs-check.md deleted file mode 100644 index 0697dcdf7b4..00000000000 --- a/.changeset/cold-cheetahs-check.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`CircularBuffer`: Add a data structure that stores the last `N` values pushed to it. diff --git a/.changeset/cool-mangos-compare.md b/.changeset/cool-mangos-compare.md deleted file mode 100644 index 470ee089456..00000000000 --- a/.changeset/cool-mangos-compare.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`Math`: add an `invMod` function to get the modular multiplicative inverse of a number in Z/nZ. diff --git a/.changeset/curvy-crabs-repeat.md b/.changeset/curvy-crabs-repeat.md deleted file mode 100644 index db3ef275bb3..00000000000 --- a/.changeset/curvy-crabs-repeat.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`RSA`: Library to verify signatures according to RFC 8017 Signature Verification Operation diff --git a/.changeset/dirty-cobras-smile.md b/.changeset/dirty-cobras-smile.md deleted file mode 100644 index d71194cfc21..00000000000 --- a/.changeset/dirty-cobras-smile.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`Arrays`: add a `sort` functions for `address[]`, `bytes32[]` and `uint256[]` memory arrays. diff --git a/.changeset/eight-eyes-burn.md b/.changeset/eight-eyes-burn.md deleted file mode 100644 index 908c90c7bbf..00000000000 --- a/.changeset/eight-eyes-burn.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`GovernorCountingFractional`: Add a governor counting module that allows distributing voting power amongst 3 options (For, Against, Abstain). diff --git a/.changeset/eleven-planets-relax.md b/.changeset/eleven-planets-relax.md deleted file mode 100644 index a1f1bbf1c4e..00000000000 --- a/.changeset/eleven-planets-relax.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': patch ---- - -`TransparentUpgradeableProxy`: Make internal `_proxyAdmin()` getter have `view` visibility. diff --git a/.changeset/flat-turtles-repeat.md b/.changeset/flat-turtles-repeat.md deleted file mode 100644 index 6b627201ac9..00000000000 --- a/.changeset/flat-turtles-repeat.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`Arrays`: deprecate `findUpperBound` in favor of the new `lowerBound`. diff --git a/.changeset/fluffy-buses-jump.md b/.changeset/fluffy-buses-jump.md deleted file mode 100644 index 0525a4d8e43..00000000000 --- a/.changeset/fluffy-buses-jump.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`Comparator`: A library of comparator functions, useful for customizing the behavior of the Heap structure. diff --git a/.changeset/fluffy-steaks-exist.md b/.changeset/fluffy-steaks-exist.md deleted file mode 100644 index b625e243481..00000000000 --- a/.changeset/fluffy-steaks-exist.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': patch ---- - -`Create2`, `Clones`: Mask `computeAddress` and `cloneDeterministic` outputs to produce a clean value for an `address` type (i.e. only use 20 bytes) diff --git a/.changeset/forty-dodos-visit.md b/.changeset/forty-dodos-visit.md deleted file mode 100644 index 7d5ae747335..00000000000 --- a/.changeset/forty-dodos-visit.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`Strings`: Added a utility function for converting an address to checksummed string. diff --git a/.changeset/friendly-nails-push.md b/.changeset/friendly-nails-push.md deleted file mode 100644 index 157bf05561a..00000000000 --- a/.changeset/friendly-nails-push.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`ERC1363`: Add implementation of the token payable standard allowing execution of contract code after transfers and approvals. diff --git a/.changeset/gentle-bulldogs-turn.md b/.changeset/gentle-bulldogs-turn.md deleted file mode 100644 index 12bc87a2dfb..00000000000 --- a/.changeset/gentle-bulldogs-turn.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`DoubleEndedQueue`: Custom errors replaced with native panic codes. diff --git a/.changeset/gorgeous-badgers-vanish.md b/.changeset/gorgeous-badgers-vanish.md deleted file mode 100644 index ce75ed6ebae..00000000000 --- a/.changeset/gorgeous-badgers-vanish.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`SlotDerivation`: Add a library of methods for derivating common storage slots. diff --git a/.changeset/great-pianos-work.md b/.changeset/great-pianos-work.md deleted file mode 100644 index da54483e47e..00000000000 --- a/.changeset/great-pianos-work.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`Heap`: A data structure that implements a heap-based priority queue. diff --git a/.changeset/heavy-baboons-give.md b/.changeset/heavy-baboons-give.md deleted file mode 100644 index 5852748f81c..00000000000 --- a/.changeset/heavy-baboons-give.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`Packing`: Added a new utility for packing, extracting and replacing bytesXX values. diff --git a/.changeset/kind-planets-cough.md b/.changeset/kind-planets-cough.md deleted file mode 100644 index 988e24c4ad2..00000000000 --- a/.changeset/kind-planets-cough.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`StorageSlot`: Add primitives for operating on the transient storage space using a typed-slot representation. diff --git a/.changeset/light-news-listen.md b/.changeset/light-news-listen.md deleted file mode 100644 index 1572d908139..00000000000 --- a/.changeset/light-news-listen.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`AccessManager`: Allow the `onlyAuthorized` modifier to restrict functions added to the manager. diff --git a/.changeset/lucky-crews-eat.md b/.changeset/lucky-crews-eat.md deleted file mode 100644 index 48592b5eaf1..00000000000 --- a/.changeset/lucky-crews-eat.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`Votes`: Set `_moveDelegateVotes` visibility to internal instead of private. diff --git a/.changeset/nervous-eyes-teach.md b/.changeset/nervous-eyes-teach.md deleted file mode 100644 index f85bc66d8f7..00000000000 --- a/.changeset/nervous-eyes-teach.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`Create2`: Bubbles up returndata from a deployed contract that reverted during construction. diff --git a/.changeset/nervous-pans-grow.md b/.changeset/nervous-pans-grow.md deleted file mode 100644 index b86a075c678..00000000000 --- a/.changeset/nervous-pans-grow.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': patch ---- - -`SafeCast`: Add `toUint(bool)` for operating on `bool` values as `uint256`. diff --git a/.changeset/nice-paws-pull.md b/.changeset/nice-paws-pull.md deleted file mode 100644 index 11f48d51f82..00000000000 --- a/.changeset/nice-paws-pull.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`SafeERC20`: Add "relaxed" function for interacting with ERC-1363 functions in a way that is compatible with EOAs. diff --git a/.changeset/odd-files-protect.md b/.changeset/odd-files-protect.md deleted file mode 100644 index 8b334acfd11..00000000000 --- a/.changeset/odd-files-protect.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`Hashes`: A library with commonly used hash functions. diff --git a/.changeset/odd-lobsters-wash.md b/.changeset/odd-lobsters-wash.md deleted file mode 100644 index 578f7a42ea7..00000000000 --- a/.changeset/odd-lobsters-wash.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`P256`: Library for verification and public key recovery of P256 (aka secp256r1) signatures. diff --git a/.changeset/poor-chefs-cheat.md b/.changeset/poor-chefs-cheat.md deleted file mode 100644 index 39db3d5139c..00000000000 --- a/.changeset/poor-chefs-cheat.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`ERC721Utils` and `ERC1155Utils`: Add reusable libraries with functions to perform acceptance checks on `IERC721Receiver` and `IERC1155Receiver` implementers. diff --git a/.changeset/serious-carrots-provide.md b/.changeset/serious-carrots-provide.md deleted file mode 100644 index 60a16580d8a..00000000000 --- a/.changeset/serious-carrots-provide.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`ERC20TemporaryApproval`: Add an ERC-20 extension that implements temporary approval using transient storage, based on ERC7674 (draft). diff --git a/.changeset/shiny-poets-whisper.md b/.changeset/shiny-poets-whisper.md deleted file mode 100644 index 92497033acf..00000000000 --- a/.changeset/shiny-poets-whisper.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`Math`: Add `modExp` function that exposes the `EIP-198` precompile. Includes `uint256` and `bytes memory` versions. diff --git a/.changeset/silver-swans-promise.md b/.changeset/silver-swans-promise.md deleted file mode 100644 index 1d2ff2e9ef8..00000000000 --- a/.changeset/silver-swans-promise.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`Panic`: Add a library for reverting with panic codes. diff --git a/.changeset/smart-bugs-switch.md b/.changeset/smart-bugs-switch.md deleted file mode 100644 index 8a001ae58a1..00000000000 --- a/.changeset/smart-bugs-switch.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`Math`: Custom errors replaced with native panic codes. diff --git a/.changeset/spotty-falcons-explain.md b/.changeset/spotty-falcons-explain.md deleted file mode 100644 index 28cb95190c1..00000000000 --- a/.changeset/spotty-falcons-explain.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`Math`, `SignedMath`: Add a branchless `ternary` function that computes`cond ? a : b` in constant gas cost. diff --git a/.changeset/spotty-queens-own.md b/.changeset/spotty-queens-own.md deleted file mode 100644 index 98fb2fbc0ed..00000000000 --- a/.changeset/spotty-queens-own.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`MerkleProof`: Add variations of `verify`, `processProof`, `multiProofVerify` and `processMultiProof` (and equivalent calldata version) with support for custom hashing functions. diff --git a/.changeset/strong-singers-talk.md b/.changeset/strong-singers-talk.md deleted file mode 100644 index 7897980cbae..00000000000 --- a/.changeset/strong-singers-talk.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`Errors`: New library of common custom errors. diff --git a/.changeset/thick-pumpkins-report.md b/.changeset/thick-pumpkins-report.md deleted file mode 100644 index f17a208950c..00000000000 --- a/.changeset/thick-pumpkins-report.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`Arrays`: add new functions `lowerBound`, `upperBound`, `lowerBoundMemory` and `upperBoundMemory` for lookups in sorted arrays with potential duplicates. diff --git a/.changeset/thin-walls-drop.md b/.changeset/thin-walls-drop.md deleted file mode 100644 index 80260020256..00000000000 --- a/.changeset/thin-walls-drop.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`AccessManager`, `VestingWallet`, `TimelockController` and `ERC2771Forwarder`: Added a public `initializer` function in their corresponding upgradeable variants. diff --git a/.changeset/twenty-feet-grin.md b/.changeset/twenty-feet-grin.md deleted file mode 100644 index 69b4fe63b2e..00000000000 --- a/.changeset/twenty-feet-grin.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`Base64`: Add `encodeURL` following section 5 of RFC4648 for URL encoding diff --git a/.changeset/violet-moons-tell.md b/.changeset/violet-moons-tell.md deleted file mode 100644 index be215e1934b..00000000000 --- a/.changeset/violet-moons-tell.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`AccessControlEnumerable`: Add a `getRoleMembers` method to return all accounts that have `role`. diff --git a/.changeset/warm-sheep-cover.md b/.changeset/warm-sheep-cover.md deleted file mode 100644 index f0a2ebaa256..00000000000 --- a/.changeset/warm-sheep-cover.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`MerkleTree`: A data structure that allows inserting elements into a merkle tree and updating its root hash. diff --git a/.changeset/wise-bobcats-speak.md b/.changeset/wise-bobcats-speak.md deleted file mode 100644 index 6ecd9695723..00000000000 --- a/.changeset/wise-bobcats-speak.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`VestingWalletCliff`: Add an extension of the `VestingWallet` contract with an added cliff. diff --git a/.changeset/witty-chicken-smile.md b/.changeset/witty-chicken-smile.md deleted file mode 100644 index 6fae3e744ad..00000000000 --- a/.changeset/witty-chicken-smile.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`ReentrancyGuardTransient`: Added a variant of `ReentrancyGuard` that uses transient storage. diff --git a/.changeset/yellow-deers-walk.md b/.changeset/yellow-deers-walk.md deleted file mode 100644 index ad370b36e58..00000000000 --- a/.changeset/yellow-deers-walk.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`EnumerableMap`: add `UintToBytes32Map`, `AddressToAddressMap`, `AddressToBytes32Map` and `Bytes32ToAddressMap`. diff --git a/.changeset/yellow-moles-hammer.md b/.changeset/yellow-moles-hammer.md deleted file mode 100644 index b13971a28a9..00000000000 --- a/.changeset/yellow-moles-hammer.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`SignatureChecker`: refactor `isValidSignatureNow` to avoid validating ECDSA signatures if there is code deployed at the signer's address. diff --git a/CHANGELOG.md b/CHANGELOG.md index b64fabab220..cc52db338ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,14 @@ # Changelog + +## 5.1.0 (2024-10-17) + ### Breaking changes - `ERC1967Utils`: Removed duplicate declaration of the `Upgraded`, `AdminChanged` and `BeaconUpgraded` events. These events are still available through the `IERC1967` interface located under the `contracts/interfaces/` directory. Minimum pragma version is now 0.8.21. - `Governor`, `GovernorCountingSimple`: The `_countVote` virtual function now returns an `uint256` with the total votes casted. This change allows for more flexibility for partial and fractional voting. Upgrading users may get a compilation error that can be fixed by adding a return statement to the `_countVote` function. -### Custom error changes +#### Custom error changes This version comes with changes to the custom error identifiers. Contracts previously depending on the following errors should be replaced accordingly: @@ -18,6 +21,82 @@ This version comes with changes to the custom error identifiers. Contracts previ - `SafeERC20`: Replace generic `Error(string)` with `SafeERC20FailedOperation` if the returned data can't be decoded as `bool`. - `SafeERC20`: Replace generic `SafeERC20FailedOperation` with the revert message from the contract call if it fails. +### Changes by category + +#### General + +- `AccessManager`, `VestingWallet`, `TimelockController` and `ERC2771Forwarder`: Added a public `initializer` function in their corresponding upgradeable variants. ([#5008](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5008)) + +#### Access + +- `AccessControlEnumerable`: Add a `getRoleMembers` method to return all accounts that have `role`. ([#4546](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4546)) +- `AccessManager`: Allow the `onlyAuthorized` modifier to restrict functions added to the manager. ([#5014](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5014)) + +#### Finance + +- `VestingWalletCliff`: Add an extension of the `VestingWallet` contract with an added cliff. ([#4870](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4870)) + +#### Governance + +- `GovernorCountingFractional`: Add a governor counting module that allows distributing voting power amongst 3 options (For, Against, Abstain). ([#5045](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5045)) +- `Votes`: Set `_moveDelegateVotes` visibility to internal instead of private. ([#5007](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5007)) + +#### Proxy + +- `Clones`: Add version of `clone` and `cloneDeterministic` that support sending value at creation. ([#4936](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4936)) +- `TransparentUpgradeableProxy`: Make internal `_proxyAdmin()` getter have `view` visibility. ([#4688](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4688)) +- `ProxyAdmin`: Fixed documentation for `UPGRADE_INTERFACE_VERSION` getter. ([#5031](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5031)) + +#### Tokens + +- `ERC1363`: Add implementation of the token payable standard allowing execution of contract code after transfers and approvals. ([#4631](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4631)) +- `ERC20TemporaryApproval`: Add an ERC-20 extension that implements temporary approval using transient storage, based on ERC7674 (draft). ([#5071](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5071)) +- `SafeERC20`: Add "relaxed" function for interacting with ERC-1363 functions in a way that is compatible with EOAs. ([#4631](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4631)) +- `SafeERC20`: Document risks of `safeIncreaseAllowance` and `safeDecreaseAllowance` when associated with ERC-7674. ([#5262](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5262)) +- `ERC721Utils` and `ERC1155Utils`: Add reusable libraries with functions to perform acceptance checks on `IERC721Receiver` and `IERC1155Receiver` implementers. ([#4845](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4845)) +- `ERC1363Utils`: Add helper similar to the existing ERC721Utils and ERC1155Utils. ([#5133](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5133)) + +#### Utils + +- `Arrays`: add a `sort` functions for `address[]`, `bytes32[]` and `uint256[]` memory arrays. ([#4846](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4846)) +- `Arrays`: add new functions `lowerBound`, `upperBound`, `lowerBoundMemory` and `upperBoundMemory` for lookups in sorted arrays with potential duplicates. ([#4842](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4842)) +- `Arrays`: deprecate `findUpperBound` in favor of the new `lowerBound`. ([#4842](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4842)) +- `Base64`: Add `encodeURL` following section 5 of RFC4648 for URL encoding ([#4822](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4822)) +- `Comparator`: A library of comparator functions, useful for customizing the behavior of the Heap structure. ([#5084](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5084)) +- `Create2`: Bubbles up returndata from a deployed contract that reverted during construction. ([#5052](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5052)) +- `Create2`, `Clones`: Mask `computeAddress` and `cloneDeterministic` outputs to produce a clean value for an `address` type (i.e. only use 20 bytes) ([#4941](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4941)) +- `Errors`: New library of common custom errors. ([#4936](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4936)) +- `Hashes`: A library with commonly used hash functions. ([#3617](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3617)) +- `Packing`: Added a new utility for packing, extracting and replacing bytesXX values. ([#4992](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4992)) +- `Panic`: Add a library for reverting with panic codes. ([#3298](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3298)) +- `ReentrancyGuardTransient`: Added a variant of `ReentrancyGuard` that uses transient storage. ([#4988](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4988)) +- `Strings`: Added a utility function for converting an address to checksummed string. ([#5067](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5067)) +- `SlotDerivation`: Add a library of methods for derivating common storage slots. ([#4975](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4975)) +- `TransientSlot`: Add primitives for operating on the transient storage space using a typed-slot representation. ([#4980](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4980)) + +##### Cryptography + +- `SignatureChecker`: refactor `isValidSignatureNow` to avoid validating ECDSA signatures if there is code deployed at the signer's address. ([#4951](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4951)) +- `MerkleProof`: Add variations of `verify`, `processProof`, `multiProofVerify` and `processMultiProof` (and equivalent calldata version) with support for custom hashing functions. ([#4887](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4887)) +- `P256`: Library for verification and public key recovery of P256 (aka secp256r1) signatures. ([#4881](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4881)) +- `RSA`: Library to verify signatures according to RFC 8017 Signature Verification Operation ([#4952](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4952)) + +#### Math + +- `Math`: add an `invMod` function to get the modular multiplicative inverse of a number in Z/nZ. ([#4839](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4839)) +- `Math`: Add `modExp` function that exposes the `EIP-198` precompile. Includes `uint256` and `bytes memory` versions. ([#3298](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3298)) +- `Math`: Custom errors replaced with native panic codes. ([#3298](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3298)) +- `Math`, `SignedMath`: Add a branchless `ternary` function that computes`cond ? a : b` in constant gas cost. ([#4976](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4976)) +- `SafeCast`: Add `toUint(bool)` for operating on `bool` values as `uint256`. ([#4878](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4878)) + +#### Structures + +- `CircularBuffer`: Add a data structure that stores the last `N` values pushed to it. ([#4913](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4913)) +- `DoubleEndedQueue`: Custom errors replaced with native panic codes. ([#4872](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4872)) +- `EnumerableMap`: add `UintToBytes32Map`, `AddressToAddressMap`, `AddressToBytes32Map` and `Bytes32ToAddressMap`. ([#4843](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4843)) +- `Heap`: A data structure that implements a heap-based priority queue. ([#5084](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5084)) +- `MerkleTree`: A data structure that allows inserting elements into a merkle tree and updating its root hash. ([#3617](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3617)) + ## 5.0.2 (2024-02-29) - `Base64`: Fix issue where dirty memory located just after the input buffer is affecting the result. ([#4926](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4926)) diff --git a/contracts/access/IAccessControl.sol b/contracts/access/IAccessControl.sol index 67ea537ed8d..4c16a6ef75c 100644 --- a/contracts/access/IAccessControl.sol +++ b/contracts/access/IAccessControl.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (access/IAccessControl.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (access/IAccessControl.sol) pragma solidity ^0.8.20; diff --git a/contracts/access/Ownable2Step.sol b/contracts/access/Ownable2Step.sol index 8050ff2b030..3a0747ce7cd 100644 --- a/contracts/access/Ownable2Step.sol +++ b/contracts/access/Ownable2Step.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable2Step.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (access/Ownable2Step.sol) pragma solidity ^0.8.20; diff --git a/contracts/access/extensions/AccessControlEnumerable.sol b/contracts/access/extensions/AccessControlEnumerable.sol index 222490c9b30..b1980e364b0 100644 --- a/contracts/access/extensions/AccessControlEnumerable.sol +++ b/contracts/access/extensions/AccessControlEnumerable.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (access/extensions/AccessControlEnumerable.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (access/extensions/AccessControlEnumerable.sol) pragma solidity ^0.8.20; diff --git a/contracts/access/extensions/IAccessControlDefaultAdminRules.sol b/contracts/access/extensions/IAccessControlDefaultAdminRules.sol index b74355aad31..3740749c4b8 100644 --- a/contracts/access/extensions/IAccessControlDefaultAdminRules.sol +++ b/contracts/access/extensions/IAccessControlDefaultAdminRules.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (access/extensions/IAccessControlDefaultAdminRules.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (access/extensions/IAccessControlDefaultAdminRules.sol) pragma solidity ^0.8.20; diff --git a/contracts/access/extensions/IAccessControlEnumerable.sol b/contracts/access/extensions/IAccessControlEnumerable.sol index 11429686355..bb9bac86ee1 100644 --- a/contracts/access/extensions/IAccessControlEnumerable.sol +++ b/contracts/access/extensions/IAccessControlEnumerable.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (access/extensions/IAccessControlEnumerable.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (access/extensions/IAccessControlEnumerable.sol) pragma solidity ^0.8.20; diff --git a/contracts/access/manager/AccessManaged.sol b/contracts/access/manager/AccessManaged.sol index 41b21b0b79d..352a54def2e 100644 --- a/contracts/access/manager/AccessManaged.sol +++ b/contracts/access/manager/AccessManaged.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (access/manager/AccessManaged.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (access/manager/AccessManaged.sol) pragma solidity ^0.8.20; diff --git a/contracts/access/manager/AccessManager.sol b/contracts/access/manager/AccessManager.sol index a6e2d7aed0e..051080554c6 100644 --- a/contracts/access/manager/AccessManager.sol +++ b/contracts/access/manager/AccessManager.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (access/manager/AccessManager.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (access/manager/AccessManager.sol) pragma solidity ^0.8.20; diff --git a/contracts/access/manager/IAccessManager.sol b/contracts/access/manager/IAccessManager.sol index 2811281a319..ebcd1d614a1 100644 --- a/contracts/access/manager/IAccessManager.sol +++ b/contracts/access/manager/IAccessManager.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (access/manager/IAccessManager.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (access/manager/IAccessManager.sol) pragma solidity ^0.8.20; diff --git a/contracts/finance/VestingWallet.sol b/contracts/finance/VestingWallet.sol index 153b8fc63de..0e0321d0da2 100644 --- a/contracts/finance/VestingWallet.sol +++ b/contracts/finance/VestingWallet.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (finance/VestingWallet.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (finance/VestingWallet.sol) pragma solidity ^0.8.20; import {IERC20} from "../token/ERC20/IERC20.sol"; diff --git a/contracts/finance/VestingWalletCliff.sol b/contracts/finance/VestingWalletCliff.sol index 83d82510427..dd1da6580bd 100644 --- a/contracts/finance/VestingWalletCliff.sol +++ b/contracts/finance/VestingWalletCliff.sol @@ -1,4 +1,5 @@ // SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.1.0) (finance/VestingWalletCliff.sol) pragma solidity ^0.8.20; diff --git a/contracts/governance/Governor.sol b/contracts/governance/Governor.sol index f1851b30ee6..2add7c759fb 100644 --- a/contracts/governance/Governor.sol +++ b/contracts/governance/Governor.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (governance/Governor.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (governance/Governor.sol) pragma solidity ^0.8.20; diff --git a/contracts/governance/IGovernor.sol b/contracts/governance/IGovernor.sol index 67abfdbbae4..28f8aaac044 100644 --- a/contracts/governance/IGovernor.sol +++ b/contracts/governance/IGovernor.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (governance/IGovernor.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (governance/IGovernor.sol) pragma solidity ^0.8.20; diff --git a/contracts/governance/extensions/GovernorCountingFractional.sol b/contracts/governance/extensions/GovernorCountingFractional.sol index a3f40201fd1..d2231bb9529 100644 --- a/contracts/governance/extensions/GovernorCountingFractional.sol +++ b/contracts/governance/extensions/GovernorCountingFractional.sol @@ -1,4 +1,5 @@ // SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.1.0) (governance/extensions/GovernorCountingFractional.sol) pragma solidity ^0.8.20; diff --git a/contracts/governance/extensions/GovernorCountingSimple.sol b/contracts/governance/extensions/GovernorCountingSimple.sol index def29e34e61..0b89b24381d 100644 --- a/contracts/governance/extensions/GovernorCountingSimple.sol +++ b/contracts/governance/extensions/GovernorCountingSimple.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (governance/extensions/GovernorCountingSimple.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (governance/extensions/GovernorCountingSimple.sol) pragma solidity ^0.8.20; diff --git a/contracts/governance/extensions/GovernorStorage.sol b/contracts/governance/extensions/GovernorStorage.sol index 40f7b23d5ac..22db099924a 100644 --- a/contracts/governance/extensions/GovernorStorage.sol +++ b/contracts/governance/extensions/GovernorStorage.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (governance/extensions/GovernorStorage.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (governance/extensions/GovernorStorage.sol) pragma solidity ^0.8.20; diff --git a/contracts/governance/extensions/GovernorTimelockAccess.sol b/contracts/governance/extensions/GovernorTimelockAccess.sol index 6e2c5b84e3d..5b8429b8320 100644 --- a/contracts/governance/extensions/GovernorTimelockAccess.sol +++ b/contracts/governance/extensions/GovernorTimelockAccess.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (governance/extensions/GovernorTimelockAccess.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (governance/extensions/GovernorTimelockAccess.sol) pragma solidity ^0.8.20; diff --git a/contracts/governance/extensions/GovernorTimelockCompound.sol b/contracts/governance/extensions/GovernorTimelockCompound.sol index 77cf369ff49..309f9a4fa76 100644 --- a/contracts/governance/extensions/GovernorTimelockCompound.sol +++ b/contracts/governance/extensions/GovernorTimelockCompound.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (governance/extensions/GovernorTimelockCompound.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (governance/extensions/GovernorTimelockCompound.sol) pragma solidity ^0.8.20; diff --git a/contracts/governance/extensions/GovernorTimelockControl.sol b/contracts/governance/extensions/GovernorTimelockControl.sol index 52d4b429598..ba0953d1608 100644 --- a/contracts/governance/extensions/GovernorTimelockControl.sol +++ b/contracts/governance/extensions/GovernorTimelockControl.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (governance/extensions/GovernorTimelockControl.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (governance/extensions/GovernorTimelockControl.sol) pragma solidity ^0.8.20; diff --git a/contracts/governance/extensions/GovernorVotes.sol b/contracts/governance/extensions/GovernorVotes.sol index 16cd934355e..9aeaf1214a1 100644 --- a/contracts/governance/extensions/GovernorVotes.sol +++ b/contracts/governance/extensions/GovernorVotes.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (governance/extensions/GovernorVotes.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (governance/extensions/GovernorVotes.sol) pragma solidity ^0.8.20; diff --git a/contracts/governance/utils/Votes.sol b/contracts/governance/utils/Votes.sol index 9c3262b9789..bbbc2264ff9 100644 --- a/contracts/governance/utils/Votes.sol +++ b/contracts/governance/utils/Votes.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (governance/utils/Votes.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (governance/utils/Votes.sol) pragma solidity ^0.8.20; import {IERC5805} from "../../interfaces/IERC5805.sol"; diff --git a/contracts/interfaces/IERC1271.sol b/contracts/interfaces/IERC1271.sol index 5f5b326336e..8c239942ac8 100644 --- a/contracts/interfaces/IERC1271.sol +++ b/contracts/interfaces/IERC1271.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC1271.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/IERC1271.sol) pragma solidity ^0.8.20; diff --git a/contracts/interfaces/IERC1363.sol b/contracts/interfaces/IERC1363.sol index a5246da37ca..02de2285997 100644 --- a/contracts/interfaces/IERC1363.sol +++ b/contracts/interfaces/IERC1363.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC1363.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/IERC1363.sol) pragma solidity ^0.8.20; diff --git a/contracts/interfaces/IERC1363Receiver.sol b/contracts/interfaces/IERC1363Receiver.sol index 279178849d6..02c065861cc 100644 --- a/contracts/interfaces/IERC1363Receiver.sol +++ b/contracts/interfaces/IERC1363Receiver.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC1363Receiver.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/IERC1363Receiver.sol) pragma solidity ^0.8.20; diff --git a/contracts/interfaces/IERC1363Spender.sol b/contracts/interfaces/IERC1363Spender.sol index 4e9aba7d7f7..13af938f066 100644 --- a/contracts/interfaces/IERC1363Spender.sol +++ b/contracts/interfaces/IERC1363Spender.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC1363Spender.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/IERC1363Spender.sol) pragma solidity ^0.8.20; diff --git a/contracts/interfaces/IERC1820Implementer.sol b/contracts/interfaces/IERC1820Implementer.sol index 9cf941a3343..95289c65cf4 100644 --- a/contracts/interfaces/IERC1820Implementer.sol +++ b/contracts/interfaces/IERC1820Implementer.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC1820Implementer.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/IERC1820Implementer.sol) pragma solidity ^0.8.20; diff --git a/contracts/interfaces/IERC1820Registry.sol b/contracts/interfaces/IERC1820Registry.sol index b8f3d73998c..fa70466114d 100644 --- a/contracts/interfaces/IERC1820Registry.sol +++ b/contracts/interfaces/IERC1820Registry.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC1820Registry.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/IERC1820Registry.sol) pragma solidity ^0.8.20; diff --git a/contracts/interfaces/IERC2981.sol b/contracts/interfaces/IERC2981.sol index 22b951dd84e..db5eb5cd446 100644 --- a/contracts/interfaces/IERC2981.sol +++ b/contracts/interfaces/IERC2981.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC2981.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/IERC2981.sol) pragma solidity ^0.8.20; diff --git a/contracts/interfaces/IERC3156FlashBorrower.sol b/contracts/interfaces/IERC3156FlashBorrower.sol index 4fd10e7cfea..daafb17ee05 100644 --- a/contracts/interfaces/IERC3156FlashBorrower.sol +++ b/contracts/interfaces/IERC3156FlashBorrower.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC3156FlashBorrower.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/IERC3156FlashBorrower.sol) pragma solidity ^0.8.20; diff --git a/contracts/interfaces/IERC3156FlashLender.sol b/contracts/interfaces/IERC3156FlashLender.sol index 47208ac3112..7b1b071d4df 100644 --- a/contracts/interfaces/IERC3156FlashLender.sol +++ b/contracts/interfaces/IERC3156FlashLender.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC3156FlashLender.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/IERC3156FlashLender.sol) pragma solidity ^0.8.20; diff --git a/contracts/interfaces/IERC4626.sol b/contracts/interfaces/IERC4626.sol index 9a24507a7fd..8ebadd72fcf 100644 --- a/contracts/interfaces/IERC4626.sol +++ b/contracts/interfaces/IERC4626.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC4626.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/IERC4626.sol) pragma solidity ^0.8.20; diff --git a/contracts/interfaces/IERC4906.sol b/contracts/interfaces/IERC4906.sol index cdcace31fcb..6ecd061347b 100644 --- a/contracts/interfaces/IERC4906.sol +++ b/contracts/interfaces/IERC4906.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC4906.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/IERC4906.sol) pragma solidity ^0.8.20; diff --git a/contracts/interfaces/IERC777.sol b/contracts/interfaces/IERC777.sol index 31f05aa6421..1e672330a41 100644 --- a/contracts/interfaces/IERC777.sol +++ b/contracts/interfaces/IERC777.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC777.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/IERC777.sol) pragma solidity ^0.8.20; diff --git a/contracts/interfaces/IERC777Recipient.sol b/contracts/interfaces/IERC777Recipient.sol index 1619e112679..c377de971fc 100644 --- a/contracts/interfaces/IERC777Recipient.sol +++ b/contracts/interfaces/IERC777Recipient.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC777Recipient.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/IERC777Recipient.sol) pragma solidity ^0.8.20; diff --git a/contracts/interfaces/IERC777Sender.sol b/contracts/interfaces/IERC777Sender.sol index f47a7832399..0ec8c278484 100644 --- a/contracts/interfaces/IERC777Sender.sol +++ b/contracts/interfaces/IERC777Sender.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC777Sender.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/IERC777Sender.sol) pragma solidity ^0.8.20; diff --git a/contracts/interfaces/draft-IERC1822.sol b/contracts/interfaces/draft-IERC1822.sol index ad047dae600..f846ea6bf0e 100644 --- a/contracts/interfaces/draft-IERC1822.sol +++ b/contracts/interfaces/draft-IERC1822.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/draft-IERC1822.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/draft-IERC1822.sol) pragma solidity ^0.8.20; diff --git a/contracts/interfaces/draft-IERC6093.sol b/contracts/interfaces/draft-IERC6093.sol index 75fd75643e6..3227fd624fc 100644 --- a/contracts/interfaces/draft-IERC6093.sol +++ b/contracts/interfaces/draft-IERC6093.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/draft-IERC6093.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/draft-IERC6093.sol) pragma solidity ^0.8.20; /** diff --git a/contracts/interfaces/draft-IERC7674.sol b/contracts/interfaces/draft-IERC7674.sol index 949ec806e84..be3c413ec36 100644 --- a/contracts/interfaces/draft-IERC7674.sol +++ b/contracts/interfaces/draft-IERC7674.sol @@ -1,4 +1,5 @@ // SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/draft-IERC7674.sol) pragma solidity ^0.8.20; diff --git a/contracts/metatx/ERC2771Context.sol b/contracts/metatx/ERC2771Context.sol index d448b24b128..794bfb3e5ef 100644 --- a/contracts/metatx/ERC2771Context.sol +++ b/contracts/metatx/ERC2771Context.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.1) (metatx/ERC2771Context.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (metatx/ERC2771Context.sol) pragma solidity ^0.8.20; diff --git a/contracts/metatx/ERC2771Forwarder.sol b/contracts/metatx/ERC2771Forwarder.sol index 0b0d185d87e..4a069874ecb 100644 --- a/contracts/metatx/ERC2771Forwarder.sol +++ b/contracts/metatx/ERC2771Forwarder.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (metatx/ERC2771Forwarder.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (metatx/ERC2771Forwarder.sol) pragma solidity ^0.8.20; diff --git a/contracts/package.json b/contracts/package.json index 845e8c4035d..e0ed163d05a 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -1,7 +1,7 @@ { "name": "@openzeppelin/contracts", "description": "Secure Smart Contract library for Solidity", - "version": "5.0.2", + "version": "5.1.0", "files": [ "**/*.sol", "/build/contracts/*.json", diff --git a/contracts/proxy/Clones.sol b/contracts/proxy/Clones.sol index 454d7fb97d2..99e25ab46db 100644 --- a/contracts/proxy/Clones.sol +++ b/contracts/proxy/Clones.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (proxy/Clones.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (proxy/Clones.sol) pragma solidity ^0.8.20; diff --git a/contracts/proxy/ERC1967/ERC1967Proxy.sol b/contracts/proxy/ERC1967/ERC1967Proxy.sol index 8f6b717a56f..4f51cd9578b 100644 --- a/contracts/proxy/ERC1967/ERC1967Proxy.sol +++ b/contracts/proxy/ERC1967/ERC1967Proxy.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (proxy/ERC1967/ERC1967Proxy.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (proxy/ERC1967/ERC1967Proxy.sol) pragma solidity ^0.8.20; diff --git a/contracts/proxy/ERC1967/ERC1967Utils.sol b/contracts/proxy/ERC1967/ERC1967Utils.sol index cf555098af9..1f320135277 100644 --- a/contracts/proxy/ERC1967/ERC1967Utils.sol +++ b/contracts/proxy/ERC1967/ERC1967Utils.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (proxy/ERC1967/ERC1967Utils.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (proxy/ERC1967/ERC1967Utils.sol) pragma solidity ^0.8.21; diff --git a/contracts/proxy/beacon/BeaconProxy.sol b/contracts/proxy/beacon/BeaconProxy.sol index 9b3f627b19a..2606f21db08 100644 --- a/contracts/proxy/beacon/BeaconProxy.sol +++ b/contracts/proxy/beacon/BeaconProxy.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (proxy/beacon/BeaconProxy.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (proxy/beacon/BeaconProxy.sol) pragma solidity ^0.8.20; diff --git a/contracts/proxy/transparent/ProxyAdmin.sol b/contracts/proxy/transparent/ProxyAdmin.sol index 7fbdca6fd7e..31772350392 100644 --- a/contracts/proxy/transparent/ProxyAdmin.sol +++ b/contracts/proxy/transparent/ProxyAdmin.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (proxy/transparent/ProxyAdmin.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (proxy/transparent/ProxyAdmin.sol) pragma solidity ^0.8.20; diff --git a/contracts/proxy/transparent/TransparentUpgradeableProxy.sol b/contracts/proxy/transparent/TransparentUpgradeableProxy.sol index 5304f4311bc..a35a725f2b3 100644 --- a/contracts/proxy/transparent/TransparentUpgradeableProxy.sol +++ b/contracts/proxy/transparent/TransparentUpgradeableProxy.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (proxy/transparent/TransparentUpgradeableProxy.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (proxy/transparent/TransparentUpgradeableProxy.sol) pragma solidity ^0.8.20; diff --git a/contracts/proxy/utils/UUPSUpgradeable.sol b/contracts/proxy/utils/UUPSUpgradeable.sol index 20eb1f72604..dc799962cb3 100644 --- a/contracts/proxy/utils/UUPSUpgradeable.sol +++ b/contracts/proxy/utils/UUPSUpgradeable.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (proxy/utils/UUPSUpgradeable.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (proxy/utils/UUPSUpgradeable.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC1155/ERC1155.sol b/contracts/token/ERC1155/ERC1155.sol index b0e7e547ab2..3e0a91f8bcc 100644 --- a/contracts/token/ERC1155/ERC1155.sol +++ b/contracts/token/ERC1155/ERC1155.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC1155/ERC1155.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC1155/ERC1155.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC1155/IERC1155.sol b/contracts/token/ERC1155/IERC1155.sol index 5a1805fadee..0da320fbefb 100644 --- a/contracts/token/ERC1155/IERC1155.sol +++ b/contracts/token/ERC1155/IERC1155.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.1) (token/ERC1155/IERC1155.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC1155/IERC1155.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC1155/IERC1155Receiver.sol b/contracts/token/ERC1155/IERC1155Receiver.sol index 36ad4c75206..7d9bc239719 100644 --- a/contracts/token/ERC1155/IERC1155Receiver.sol +++ b/contracts/token/ERC1155/IERC1155Receiver.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC1155/IERC1155Receiver.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC1155/IERC1155Receiver.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC1155/extensions/ERC1155Pausable.sol b/contracts/token/ERC1155/extensions/ERC1155Pausable.sol index e99cf2aa95d..a0de999f0cf 100644 --- a/contracts/token/ERC1155/extensions/ERC1155Pausable.sol +++ b/contracts/token/ERC1155/extensions/ERC1155Pausable.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC1155/extensions/ERC1155Pausable.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC1155/extensions/ERC1155Pausable.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC1155/extensions/ERC1155Supply.sol b/contracts/token/ERC1155/extensions/ERC1155Supply.sol index 33c5b103a95..00dd082a383 100644 --- a/contracts/token/ERC1155/extensions/ERC1155Supply.sol +++ b/contracts/token/ERC1155/extensions/ERC1155Supply.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC1155/extensions/ERC1155Supply.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC1155/extensions/ERC1155Supply.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC1155/extensions/ERC1155URIStorage.sol b/contracts/token/ERC1155/extensions/ERC1155URIStorage.sol index e8436e830d7..5abf319d327 100644 --- a/contracts/token/ERC1155/extensions/ERC1155URIStorage.sol +++ b/contracts/token/ERC1155/extensions/ERC1155URIStorage.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC1155/extensions/ERC1155URIStorage.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC1155/extensions/ERC1155URIStorage.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol b/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol index ea07897b9f2..b413f4304d3 100644 --- a/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol +++ b/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC1155/extensions/IERC1155MetadataURI.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC1155/extensions/IERC1155MetadataURI.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC1155/utils/ERC1155Holder.sol b/contracts/token/ERC1155/utils/ERC1155Holder.sol index 35be58c5238..7ad5943aceb 100644 --- a/contracts/token/ERC1155/utils/ERC1155Holder.sol +++ b/contracts/token/ERC1155/utils/ERC1155Holder.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC1155/utils/ERC1155Holder.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC1155/utils/ERC1155Holder.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC1155/utils/ERC1155Utils.sol b/contracts/token/ERC1155/utils/ERC1155Utils.sol index 62accf6bc6e..371cd86ba46 100644 --- a/contracts/token/ERC1155/utils/ERC1155Utils.sol +++ b/contracts/token/ERC1155/utils/ERC1155Utils.sol @@ -1,4 +1,5 @@ // SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC1155/utils/ERC1155Utils.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC20/ERC20.sol b/contracts/token/ERC20/ERC20.sol index 9885eaac996..0b707604c0c 100644 --- a/contracts/token/ERC20/ERC20.sol +++ b/contracts/token/ERC20/ERC20.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/ERC20.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/ERC20.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC20/IERC20.sol b/contracts/token/ERC20/IERC20.sol index d8907216425..7d1019563f5 100644 --- a/contracts/token/ERC20/IERC20.sol +++ b/contracts/token/ERC20/IERC20.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/IERC20.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC20/extensions/ERC1363.sol b/contracts/token/ERC20/extensions/ERC1363.sol index 4ae78f32764..acc841d78fc 100644 --- a/contracts/token/ERC20/extensions/ERC1363.sol +++ b/contracts/token/ERC20/extensions/ERC1363.sol @@ -1,4 +1,5 @@ // SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/extensions/ERC1363.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC20/extensions/ERC20FlashMint.sol b/contracts/token/ERC20/extensions/ERC20FlashMint.sol index fd8ff0b3ece..4d3a31f6df2 100644 --- a/contracts/token/ERC20/extensions/ERC20FlashMint.sol +++ b/contracts/token/ERC20/extensions/ERC20FlashMint.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/ERC20FlashMint.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/extensions/ERC20FlashMint.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC20/extensions/ERC20Pausable.sol b/contracts/token/ERC20/extensions/ERC20Pausable.sol index 9b56f053566..2f6d86c4a53 100644 --- a/contracts/token/ERC20/extensions/ERC20Pausable.sol +++ b/contracts/token/ERC20/extensions/ERC20Pausable.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/ERC20Pausable.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/extensions/ERC20Pausable.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC20/extensions/ERC20Permit.sol b/contracts/token/ERC20/extensions/ERC20Permit.sol index 22b19dc0bb7..3d36561a85f 100644 --- a/contracts/token/ERC20/extensions/ERC20Permit.sol +++ b/contracts/token/ERC20/extensions/ERC20Permit.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/ERC20Permit.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/extensions/ERC20Permit.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC20/extensions/ERC20Votes.sol b/contracts/token/ERC20/extensions/ERC20Votes.sol index 7680293a7cb..c15e7f56885 100644 --- a/contracts/token/ERC20/extensions/ERC20Votes.sol +++ b/contracts/token/ERC20/extensions/ERC20Votes.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/ERC20Votes.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/extensions/ERC20Votes.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC20/extensions/ERC20Wrapper.sol b/contracts/token/ERC20/extensions/ERC20Wrapper.sol index eecd6ab7a63..9cc5aaf5f68 100644 --- a/contracts/token/ERC20/extensions/ERC20Wrapper.sol +++ b/contracts/token/ERC20/extensions/ERC20Wrapper.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/ERC20Wrapper.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/extensions/ERC20Wrapper.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC20/extensions/ERC4626.sol b/contracts/token/ERC20/extensions/ERC4626.sol index 73778b530ca..ec9a255076c 100644 --- a/contracts/token/ERC20/extensions/ERC4626.sol +++ b/contracts/token/ERC20/extensions/ERC4626.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/ERC4626.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/extensions/ERC4626.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC20/extensions/IERC20Metadata.sol b/contracts/token/ERC20/extensions/IERC20Metadata.sol index e8c020b1fcd..3c067ef4012 100644 --- a/contracts/token/ERC20/extensions/IERC20Metadata.sol +++ b/contracts/token/ERC20/extensions/IERC20Metadata.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Metadata.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/extensions/IERC20Metadata.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC20/extensions/IERC20Permit.sol b/contracts/token/ERC20/extensions/IERC20Permit.sol index a8ad26ede5a..fc374368fd2 100644 --- a/contracts/token/ERC20/extensions/IERC20Permit.sol +++ b/contracts/token/ERC20/extensions/IERC20Permit.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Permit.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/extensions/IERC20Permit.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC20/extensions/draft-ERC20TemporaryApproval.sol b/contracts/token/ERC20/extensions/draft-ERC20TemporaryApproval.sol index c5a29685c7f..d30521b49bb 100644 --- a/contracts/token/ERC20/extensions/draft-ERC20TemporaryApproval.sol +++ b/contracts/token/ERC20/extensions/draft-ERC20TemporaryApproval.sol @@ -1,4 +1,5 @@ // SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/extensions/draft-ERC20TemporaryApproval.sol) pragma solidity ^0.8.24; diff --git a/contracts/token/ERC20/utils/ERC1363Utils.sol b/contracts/token/ERC20/utils/ERC1363Utils.sol index f5c931361f7..6ba26901eb0 100644 --- a/contracts/token/ERC20/utils/ERC1363Utils.sol +++ b/contracts/token/ERC20/utils/ERC1363Utils.sol @@ -1,4 +1,5 @@ // SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/utils/ERC1363Utils.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC20/utils/SafeERC20.sol b/contracts/token/ERC20/utils/SafeERC20.sol index 19c2f3e19e8..eb2f903fbed 100644 --- a/contracts/token/ERC20/utils/SafeERC20.sol +++ b/contracts/token/ERC20/utils/SafeERC20.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/utils/SafeERC20.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/utils/SafeERC20.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC721/ERC721.sol b/contracts/token/ERC721/ERC721.sol index df7ff50575d..6aebc37309f 100644 --- a/contracts/token/ERC721/ERC721.sol +++ b/contracts/token/ERC721/ERC721.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/ERC721.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC721/ERC721.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC721/IERC721.sol b/contracts/token/ERC721/IERC721.sol index d6ab6a47d2a..da393014753 100644 --- a/contracts/token/ERC721/IERC721.sol +++ b/contracts/token/ERC721/IERC721.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/IERC721.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC721/IERC721.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC721/IERC721Receiver.sol b/contracts/token/ERC721/IERC721Receiver.sol index a53d0777479..d472eec338a 100644 --- a/contracts/token/ERC721/IERC721Receiver.sol +++ b/contracts/token/ERC721/IERC721Receiver.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/IERC721Receiver.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC721/IERC721Receiver.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC721/extensions/ERC721Burnable.sol b/contracts/token/ERC721/extensions/ERC721Burnable.sol index 65cfc744cba..c6d22455761 100644 --- a/contracts/token/ERC721/extensions/ERC721Burnable.sol +++ b/contracts/token/ERC721/extensions/ERC721Burnable.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/extensions/ERC721Burnable.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC721/extensions/ERC721Burnable.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC721/extensions/ERC721Consecutive.sol b/contracts/token/ERC721/extensions/ERC721Consecutive.sol index 8508a79f4fc..6b849d77438 100644 --- a/contracts/token/ERC721/extensions/ERC721Consecutive.sol +++ b/contracts/token/ERC721/extensions/ERC721Consecutive.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/extensions/ERC721Consecutive.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC721/extensions/ERC721Consecutive.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC721/extensions/ERC721Enumerable.sol b/contracts/token/ERC721/extensions/ERC721Enumerable.sol index 1bd4375290d..43aa81e6e09 100644 --- a/contracts/token/ERC721/extensions/ERC721Enumerable.sol +++ b/contracts/token/ERC721/extensions/ERC721Enumerable.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/extensions/ERC721Enumerable.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC721/extensions/ERC721Enumerable.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC721/extensions/ERC721Pausable.sol b/contracts/token/ERC721/extensions/ERC721Pausable.sol index 81619c7f5dc..9a75623c62a 100644 --- a/contracts/token/ERC721/extensions/ERC721Pausable.sol +++ b/contracts/token/ERC721/extensions/ERC721Pausable.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/extensions/ERC721Pausable.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC721/extensions/ERC721Pausable.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC721/extensions/ERC721Royalty.sol b/contracts/token/ERC721/extensions/ERC721Royalty.sol index 1e0b25af43b..cfce1786c7a 100644 --- a/contracts/token/ERC721/extensions/ERC721Royalty.sol +++ b/contracts/token/ERC721/extensions/ERC721Royalty.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/extensions/ERC721Royalty.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC721/extensions/ERC721Royalty.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC721/extensions/ERC721URIStorage.sol b/contracts/token/ERC721/extensions/ERC721URIStorage.sol index 562f815c0b8..d8b4d8d1f91 100644 --- a/contracts/token/ERC721/extensions/ERC721URIStorage.sol +++ b/contracts/token/ERC721/extensions/ERC721URIStorage.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/extensions/ERC721URIStorage.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC721/extensions/ERC721URIStorage.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC721/extensions/ERC721Votes.sol b/contracts/token/ERC721/extensions/ERC721Votes.sol index 4962cb00c19..f71195ce7c8 100644 --- a/contracts/token/ERC721/extensions/ERC721Votes.sol +++ b/contracts/token/ERC721/extensions/ERC721Votes.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/extensions/ERC721Votes.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC721/extensions/ERC721Votes.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC721/extensions/ERC721Wrapper.sol b/contracts/token/ERC721/extensions/ERC721Wrapper.sol index 0a8acacb87b..111136bbe77 100644 --- a/contracts/token/ERC721/extensions/ERC721Wrapper.sol +++ b/contracts/token/ERC721/extensions/ERC721Wrapper.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/extensions/ERC721Wrapper.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC721/extensions/ERC721Wrapper.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC721/utils/ERC721Utils.sol b/contracts/token/ERC721/utils/ERC721Utils.sol index 0e18f5cc0a8..2fd091afd67 100644 --- a/contracts/token/ERC721/utils/ERC721Utils.sol +++ b/contracts/token/ERC721/utils/ERC721Utils.sol @@ -1,4 +1,5 @@ // SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC721/utils/ERC721Utils.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/common/ERC2981.sol b/contracts/token/common/ERC2981.sol index 7f56b275ee3..8335e56efab 100644 --- a/contracts/token/common/ERC2981.sol +++ b/contracts/token/common/ERC2981.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (token/common/ERC2981.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (token/common/ERC2981.sol) pragma solidity ^0.8.20; diff --git a/contracts/utils/Address.sol b/contracts/utils/Address.sol index 40f01a93dca..a1c8af296ce 100644 --- a/contracts/utils/Address.sol +++ b/contracts/utils/Address.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (utils/Address.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (utils/Address.sol) pragma solidity ^0.8.20; diff --git a/contracts/utils/Arrays.sol b/contracts/utils/Arrays.sol index 432c8602800..bd3401703d4 100644 --- a/contracts/utils/Arrays.sol +++ b/contracts/utils/Arrays.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (utils/Arrays.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (utils/Arrays.sol) // This file was procedurally generated from scripts/generate/templates/Arrays.js. pragma solidity ^0.8.20; diff --git a/contracts/utils/Base64.sol b/contracts/utils/Base64.sol index ef9431b5578..8b7c5c5ed06 100644 --- a/contracts/utils/Base64.sol +++ b/contracts/utils/Base64.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.2) (utils/Base64.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (utils/Base64.sol) pragma solidity ^0.8.20; diff --git a/contracts/utils/Comparators.sol b/contracts/utils/Comparators.sol index c10734712bc..a8c5e73dfea 100644 --- a/contracts/utils/Comparators.sol +++ b/contracts/utils/Comparators.sol @@ -1,4 +1,5 @@ // SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.1.0) (utils/Comparators.sol) pragma solidity ^0.8.20; diff --git a/contracts/utils/Create2.sol b/contracts/utils/Create2.sol index 121eb0c742a..ffd39d9a46c 100644 --- a/contracts/utils/Create2.sol +++ b/contracts/utils/Create2.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (utils/Create2.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (utils/Create2.sol) pragma solidity ^0.8.20; diff --git a/contracts/utils/Errors.sol b/contracts/utils/Errors.sol index 715db953620..442fc18920f 100644 --- a/contracts/utils/Errors.sol +++ b/contracts/utils/Errors.sol @@ -1,4 +1,5 @@ // SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.1.0) (utils/Errors.sol) pragma solidity ^0.8.20; diff --git a/contracts/utils/Packing.sol b/contracts/utils/Packing.sol index 4da311cf3c0..069153bef4a 100644 --- a/contracts/utils/Packing.sol +++ b/contracts/utils/Packing.sol @@ -1,4 +1,5 @@ // SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.1.0) (utils/Packing.sol) // This file was procedurally generated from scripts/generate/templates/Packing.js. pragma solidity ^0.8.20; diff --git a/contracts/utils/Panic.sol b/contracts/utils/Panic.sol index 0e3c38c3e36..e168824d34b 100644 --- a/contracts/utils/Panic.sol +++ b/contracts/utils/Panic.sol @@ -1,4 +1,5 @@ // SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.1.0) (utils/Panic.sol) pragma solidity ^0.8.20; diff --git a/contracts/utils/ReentrancyGuard.sol b/contracts/utils/ReentrancyGuard.sol index 081851170e2..a95fb512f31 100644 --- a/contracts/utils/ReentrancyGuard.sol +++ b/contracts/utils/ReentrancyGuard.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (utils/ReentrancyGuard.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (utils/ReentrancyGuard.sol) pragma solidity ^0.8.20; diff --git a/contracts/utils/ReentrancyGuardTransient.sol b/contracts/utils/ReentrancyGuardTransient.sol index ba8cbb65b1f..1a62e29dafa 100644 --- a/contracts/utils/ReentrancyGuardTransient.sol +++ b/contracts/utils/ReentrancyGuardTransient.sol @@ -1,4 +1,5 @@ // SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.1.0) (utils/ReentrancyGuardTransient.sol) pragma solidity ^0.8.24; diff --git a/contracts/utils/ShortStrings.sol b/contracts/utils/ShortStrings.sol index 78fc5cd963d..fb8bde51668 100644 --- a/contracts/utils/ShortStrings.sol +++ b/contracts/utils/ShortStrings.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (utils/ShortStrings.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (utils/ShortStrings.sol) pragma solidity ^0.8.20; diff --git a/contracts/utils/SlotDerivation.sol b/contracts/utils/SlotDerivation.sol index 83b9d5639c0..62d1545c650 100644 --- a/contracts/utils/SlotDerivation.sol +++ b/contracts/utils/SlotDerivation.sol @@ -1,4 +1,5 @@ // SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.1.0) (utils/SlotDerivation.sol) // This file was procedurally generated from scripts/generate/templates/SlotDerivation.js. pragma solidity ^0.8.20; diff --git a/contracts/utils/StorageSlot.sol b/contracts/utils/StorageSlot.sol index 2df32cb9278..aebb10524a2 100644 --- a/contracts/utils/StorageSlot.sol +++ b/contracts/utils/StorageSlot.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (utils/StorageSlot.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (utils/StorageSlot.sol) // This file was procedurally generated from scripts/generate/templates/StorageSlot.js. pragma solidity ^0.8.20; diff --git a/contracts/utils/Strings.sol b/contracts/utils/Strings.sol index b72588646f7..c353212927e 100644 --- a/contracts/utils/Strings.sol +++ b/contracts/utils/Strings.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (utils/Strings.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (utils/Strings.sol) pragma solidity ^0.8.20; diff --git a/contracts/utils/TransientSlot.sol b/contracts/utils/TransientSlot.sol index f1f67bdceb0..25c57dd3f96 100644 --- a/contracts/utils/TransientSlot.sol +++ b/contracts/utils/TransientSlot.sol @@ -1,4 +1,5 @@ // SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.1.0) (utils/TransientSlot.sol) // This file was procedurally generated from scripts/generate/templates/TransientSlot.js. pragma solidity ^0.8.24; diff --git a/contracts/utils/cryptography/ECDSA.sol b/contracts/utils/cryptography/ECDSA.sol index dc6534a8aa0..6493f56338a 100644 --- a/contracts/utils/cryptography/ECDSA.sol +++ b/contracts/utils/cryptography/ECDSA.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (utils/cryptography/ECDSA.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (utils/cryptography/ECDSA.sol) pragma solidity ^0.8.20; diff --git a/contracts/utils/cryptography/EIP712.sol b/contracts/utils/cryptography/EIP712.sol index 77c4c8990fe..f15a67bd9b5 100644 --- a/contracts/utils/cryptography/EIP712.sol +++ b/contracts/utils/cryptography/EIP712.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (utils/cryptography/EIP712.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (utils/cryptography/EIP712.sol) pragma solidity ^0.8.20; diff --git a/contracts/utils/cryptography/Hashes.sol b/contracts/utils/cryptography/Hashes.sol index 0ed89b308c0..893883164fb 100644 --- a/contracts/utils/cryptography/Hashes.sol +++ b/contracts/utils/cryptography/Hashes.sol @@ -1,4 +1,5 @@ // SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.1.0) (utils/cryptography/Hashes.sol) pragma solidity ^0.8.20; diff --git a/contracts/utils/cryptography/MerkleProof.sol b/contracts/utils/cryptography/MerkleProof.sol index a8658013592..19b09e2af65 100644 --- a/contracts/utils/cryptography/MerkleProof.sol +++ b/contracts/utils/cryptography/MerkleProof.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (utils/cryptography/MerkleProof.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (utils/cryptography/MerkleProof.sol) // This file was procedurally generated from scripts/generate/templates/MerkleProof.js. pragma solidity ^0.8.20; diff --git a/contracts/utils/cryptography/MessageHashUtils.sol b/contracts/utils/cryptography/MessageHashUtils.sol index 35746ce72ce..e1cbccb65ec 100644 --- a/contracts/utils/cryptography/MessageHashUtils.sol +++ b/contracts/utils/cryptography/MessageHashUtils.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (utils/cryptography/MessageHashUtils.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (utils/cryptography/MessageHashUtils.sol) pragma solidity ^0.8.20; diff --git a/contracts/utils/cryptography/P256.sol b/contracts/utils/cryptography/P256.sol index 3028505ba75..510eb55e700 100644 --- a/contracts/utils/cryptography/P256.sol +++ b/contracts/utils/cryptography/P256.sol @@ -1,4 +1,5 @@ // SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.1.0) (utils/cryptography/P256.sol) pragma solidity ^0.8.20; import {Math} from "../math/Math.sol"; diff --git a/contracts/utils/cryptography/RSA.sol b/contracts/utils/cryptography/RSA.sol index 70c38fd15bb..4e04ce5cc56 100644 --- a/contracts/utils/cryptography/RSA.sol +++ b/contracts/utils/cryptography/RSA.sol @@ -1,4 +1,5 @@ // SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.1.0) (utils/cryptography/RSA.sol) pragma solidity ^0.8.20; import {Math} from "../math/Math.sol"; diff --git a/contracts/utils/cryptography/SignatureChecker.sol b/contracts/utils/cryptography/SignatureChecker.sol index 9aaa2e0716c..554f00d89d1 100644 --- a/contracts/utils/cryptography/SignatureChecker.sol +++ b/contracts/utils/cryptography/SignatureChecker.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (utils/cryptography/SignatureChecker.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (utils/cryptography/SignatureChecker.sol) pragma solidity ^0.8.20; diff --git a/contracts/utils/introspection/ERC165.sol b/contracts/utils/introspection/ERC165.sol index 664b39fc19d..9fbce0447e7 100644 --- a/contracts/utils/introspection/ERC165.sol +++ b/contracts/utils/introspection/ERC165.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/ERC165.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/ERC165.sol) pragma solidity ^0.8.20; diff --git a/contracts/utils/introspection/ERC165Checker.sol b/contracts/utils/introspection/ERC165Checker.sol index a0ac72c21bf..8650f5503cc 100644 --- a/contracts/utils/introspection/ERC165Checker.sol +++ b/contracts/utils/introspection/ERC165Checker.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/ERC165Checker.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/ERC165Checker.sol) pragma solidity ^0.8.20; diff --git a/contracts/utils/introspection/IERC165.sol b/contracts/utils/introspection/IERC165.sol index bfbdf5dda39..719ec358659 100644 --- a/contracts/utils/introspection/IERC165.sol +++ b/contracts/utils/introspection/IERC165.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/IERC165.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/IERC165.sol) pragma solidity ^0.8.20; diff --git a/contracts/utils/math/Math.sol b/contracts/utils/math/Math.sol index 9e277f3f50d..85a420b1a7b 100644 --- a/contracts/utils/math/Math.sol +++ b/contracts/utils/math/Math.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/Math.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (utils/math/Math.sol) pragma solidity ^0.8.20; diff --git a/contracts/utils/math/SafeCast.sol b/contracts/utils/math/SafeCast.sol index 36832006ebf..b345ede1e69 100644 --- a/contracts/utils/math/SafeCast.sol +++ b/contracts/utils/math/SafeCast.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/SafeCast.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (utils/math/SafeCast.sol) // This file was procedurally generated from scripts/generate/templates/SafeCast.js. pragma solidity ^0.8.20; diff --git a/contracts/utils/math/SignedMath.sol b/contracts/utils/math/SignedMath.sol index 502f4ecb03f..7c97aa4c22d 100644 --- a/contracts/utils/math/SignedMath.sol +++ b/contracts/utils/math/SignedMath.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/SignedMath.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (utils/math/SignedMath.sol) pragma solidity ^0.8.20; diff --git a/contracts/utils/structs/Checkpoints.sol b/contracts/utils/structs/Checkpoints.sol index 39e18a1fe82..8d8f130319d 100644 --- a/contracts/utils/structs/Checkpoints.sol +++ b/contracts/utils/structs/Checkpoints.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (utils/structs/Checkpoints.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (utils/structs/Checkpoints.sol) // This file was procedurally generated from scripts/generate/templates/Checkpoints.js. pragma solidity ^0.8.20; diff --git a/contracts/utils/structs/CircularBuffer.sol b/contracts/utils/structs/CircularBuffer.sol index cf1afbbd6ea..ae0a4a88f68 100644 --- a/contracts/utils/structs/CircularBuffer.sol +++ b/contracts/utils/structs/CircularBuffer.sol @@ -1,4 +1,5 @@ // SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.1.0) (utils/structs/CircularBuffer.sol) pragma solidity ^0.8.20; import {Math} from "../math/Math.sol"; diff --git a/contracts/utils/structs/DoubleEndedQueue.sol b/contracts/utils/structs/DoubleEndedQueue.sol index 48f0d68c1eb..f243243bbd7 100644 --- a/contracts/utils/structs/DoubleEndedQueue.sol +++ b/contracts/utils/structs/DoubleEndedQueue.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (utils/structs/DoubleEndedQueue.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (utils/structs/DoubleEndedQueue.sol) pragma solidity ^0.8.20; import {Panic} from "../Panic.sol"; diff --git a/contracts/utils/structs/EnumerableMap.sol b/contracts/utils/structs/EnumerableMap.sol index d86e57f69d5..4e12acec142 100644 --- a/contracts/utils/structs/EnumerableMap.sol +++ b/contracts/utils/structs/EnumerableMap.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (utils/structs/EnumerableMap.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (utils/structs/EnumerableMap.sol) // This file was procedurally generated from scripts/generate/templates/EnumerableMap.js. pragma solidity ^0.8.20; diff --git a/contracts/utils/structs/EnumerableSet.sol b/contracts/utils/structs/EnumerableSet.sol index 90fcfa9d77b..065202e8204 100644 --- a/contracts/utils/structs/EnumerableSet.sol +++ b/contracts/utils/structs/EnumerableSet.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (utils/structs/EnumerableSet.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (utils/structs/EnumerableSet.sol) // This file was procedurally generated from scripts/generate/templates/EnumerableSet.js. pragma solidity ^0.8.20; diff --git a/contracts/utils/structs/Heap.sol b/contracts/utils/structs/Heap.sol index d33dfbec8b2..c97bb432a33 100644 --- a/contracts/utils/structs/Heap.sol +++ b/contracts/utils/structs/Heap.sol @@ -1,4 +1,5 @@ // SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.1.0) (utils/structs/Heap.sol) pragma solidity ^0.8.20; diff --git a/contracts/utils/structs/MerkleTree.sol b/contracts/utils/structs/MerkleTree.sol index 6a78c4b3c97..56f5bc67237 100644 --- a/contracts/utils/structs/MerkleTree.sol +++ b/contracts/utils/structs/MerkleTree.sol @@ -1,4 +1,5 @@ // SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.1.0) (utils/structs/MerkleTree.sol) pragma solidity ^0.8.20; diff --git a/contracts/utils/types/Time.sol b/contracts/utils/types/Time.sol index 1f729515425..a495932de39 100644 --- a/contracts/utils/types/Time.sol +++ b/contracts/utils/types/Time.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (utils/types/Time.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (utils/types/Time.sol) pragma solidity ^0.8.20; diff --git a/package.json b/package.json index f7c0f519737..f9b88273362 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "openzeppelin-solidity", "description": "Secure Smart Contract library for Solidity", - "version": "5.0.2", + "version": "5.1.0", "private": true, "files": [ "/contracts/**/*.sol", From 378914ceabe26d149b0f413e7c6b226704d5c1ad Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Fri, 18 Oct 2024 14:17:20 +0200 Subject: [PATCH 32/84] Delegate override vote (#5192) Co-authored-by: Arr00 <13561405+arr00@users.noreply.github.com> --- .changeset/great-lions-hear.md | 5 + .changeset/pink-wasps-hammer.md | 5 + contracts/governance/Governor.sol | 11 +- contracts/governance/README.adoc | 6 + .../GovernorCountingOverridable.sol | 212 +++++++++++ .../extensions/GovernorPreventLateQuorum.sol | 16 +- contracts/governance/utils/VotesExtended.sol | 70 ++++ .../mocks/VotesAdditionalCheckpointsMock.sol | 42 +++ .../GovernorCountingOverridableMock.sol | 18 + .../GovernorPreventLateQuorumMock.sol | 10 +- .../ERC20VotesAdditionalCheckpointsMock.sol | 31 ++ .../GovernorCountingOverridable.test.js | 344 ++++++++++++++++++ .../utils/VotesAdditionalCheckpoints.test.js | 152 ++++++++ test/helpers/eip712-types.js | 7 + test/helpers/governance.js | 17 + 15 files changed, 924 insertions(+), 22 deletions(-) create mode 100644 .changeset/great-lions-hear.md create mode 100644 .changeset/pink-wasps-hammer.md create mode 100644 contracts/governance/extensions/GovernorCountingOverridable.sol create mode 100644 contracts/governance/utils/VotesExtended.sol create mode 100644 contracts/mocks/VotesAdditionalCheckpointsMock.sol create mode 100644 contracts/mocks/governance/GovernorCountingOverridableMock.sol create mode 100644 contracts/mocks/token/ERC20VotesAdditionalCheckpointsMock.sol create mode 100644 test/governance/extensions/GovernorCountingOverridable.test.js create mode 100644 test/governance/utils/VotesAdditionalCheckpoints.test.js diff --git a/.changeset/great-lions-hear.md b/.changeset/great-lions-hear.md new file mode 100644 index 00000000000..2be5de253a8 --- /dev/null +++ b/.changeset/great-lions-hear.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': patch +--- + +`VotesExtended`: Create an extension of `Votes` which checkpoints balances and delegates. diff --git a/.changeset/pink-wasps-hammer.md b/.changeset/pink-wasps-hammer.md new file mode 100644 index 00000000000..69f63d3ca19 --- /dev/null +++ b/.changeset/pink-wasps-hammer.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': patch +--- + +`GovernorCountingOverridable`: Add a governor counting module that enables token holders to override the vote of their delegate. diff --git a/contracts/governance/Governor.sol b/contracts/governance/Governor.sol index 2add7c759fb..390411556b0 100644 --- a/contracts/governance/Governor.sol +++ b/contracts/governance/Governor.sol @@ -260,6 +260,13 @@ abstract contract Governor is Context, ERC165, EIP712, Nonces, IGovernor, IERC72 bytes memory params ) internal virtual returns (uint256); + /** + * @dev Hook that should be called every time the tally for a proposal is updated. + * + * Note: This function must run successfully. Reverts will result in the bricking of governance + */ + function _tallyUpdated(uint256 proposalId) internal virtual {} + /** * @dev Default additional encoded parameters used by castVote methods that don't include them * @@ -649,6 +656,8 @@ abstract contract Governor is Context, ERC165, EIP712, Nonces, IGovernor, IERC72 emit VoteCastWithParams(account, proposalId, support, votedWeight, reason, params); } + _tallyUpdated(proposalId); + return votedWeight; } @@ -732,7 +741,7 @@ abstract contract Governor is Context, ERC165, EIP712, Nonces, IGovernor, IERC72 * * If requirements are not met, reverts with a {GovernorUnexpectedProposalState} error. */ - function _validateStateBitmap(uint256 proposalId, bytes32 allowedStates) private view returns (ProposalState) { + function _validateStateBitmap(uint256 proposalId, bytes32 allowedStates) internal view returns (ProposalState) { ProposalState currentState = state(proposalId); if (_encodeStateBitmap(currentState) & allowedStates == bytes32(0)) { revert GovernorUnexpectedProposalState(proposalId, currentState, allowedStates); diff --git a/contracts/governance/README.adoc b/contracts/governance/README.adoc index 0f556b90d69..d390d1be82d 100644 --- a/contracts/governance/README.adoc +++ b/contracts/governance/README.adoc @@ -30,6 +30,8 @@ Counting modules determine valid voting options. * {GovernorCountingFractional}: A more modular voting system that allows a user to vote with only part of its voting power, and to split that weight arbitrarily between the 3 different options (Against, For and Abstain). +* {GovernorCountingOverridable}: An extended version of `GovernorCountingSimple` which allows delegatees to override their delegates while the vote is live. + Timelock extensions add a delay for governance decisions to be executed. The workflow is extended to require a `queue` step before execution. With these modules, proposals are executed by the external timelock contract, thus it is the timelock that has to hold the assets that are being governed. * {GovernorTimelockAccess}: Connects with an instance of an {AccessManager}. This allows restrictions (and delays) enforced by the manager to be considered by the Governor and integrated into the AccessManager's "schedule + execute" workflow. @@ -66,6 +68,8 @@ NOTE: Functions of the `Governor` contract do not include access control. If you {{GovernorCountingFractional}} +{{GovernorCountingOverride}} + {{GovernorVotes}} {{GovernorVotesQuorumFraction}} @@ -88,6 +92,8 @@ NOTE: Functions of the `Governor` contract do not include access control. If you {{Votes}} +{{VotesExtended}} + == Timelock In a governance system, the {TimelockController} contract is in charge of introducing a delay between a proposal and its execution. It can be used with or without a {Governor}. diff --git a/contracts/governance/extensions/GovernorCountingOverridable.sol b/contracts/governance/extensions/GovernorCountingOverridable.sol new file mode 100644 index 00000000000..9b46903e35d --- /dev/null +++ b/contracts/governance/extensions/GovernorCountingOverridable.sol @@ -0,0 +1,212 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {SignatureChecker} from "../../utils/cryptography/SignatureChecker.sol"; +import {SafeCast} from "../../utils/math/SafeCast.sol"; +import {VotesExtended} from "../utils/VotesExtended.sol"; +import {GovernorVotes} from "./GovernorVotes.sol"; + +/** + * @dev Extension of {Governor} which enables delegatees to override the vote of their delegates. This module requires a + * token token that inherits `VotesExtended`. + */ +abstract contract GovernorCountingOverridable is GovernorVotes { + bytes32 public constant OVERRIDE_BALLOT_TYPEHASH = + keccak256("OverrideBallot(uint256 proposalId,uint8 support,address voter,uint256 nonce,string reason)"); + + /** + * @dev Supported vote types. Matches Governor Bravo ordering. + */ + enum VoteType { + Against, + For, + Abstain + } + + struct VoteReceipt { + uint8 casted; // 0 if vote was not casted. Otherwise: support + 1 + bool hasOverriden; + uint208 overridenWeight; + } + + struct ProposalVote { + uint256[3] votes; + mapping(address voter => VoteReceipt) voteReceipt; + } + + event VoteReduced(address indexed voter, uint256 proposalId, uint8 support, uint256 weight); + event OverrideVoteCast(address indexed voter, uint256 proposalId, uint8 support, uint256 weight, string reason); + + error GovernorAlreadyOverridenVote(address account); + + mapping(uint256 proposalId => ProposalVote) private _proposalVotes; + + /** + * @dev See {IGovernor-COUNTING_MODE}. + */ + // solhint-disable-next-line func-name-mixedcase + function COUNTING_MODE() public pure virtual override returns (string memory) { + return "support=bravo,override&quorum=for,abstain&overridable=true"; + } + + /** + * @dev See {IGovernor-hasVoted}. + */ + function hasVoted(uint256 proposalId, address account) public view virtual override returns (bool) { + return _proposalVotes[proposalId].voteReceipt[account].casted != 0; + } + + /** + * @dev Check if an `account` has overridden their delegate for a proposal. + */ + function hasVotedOverride(uint256 proposalId, address account) public view virtual returns (bool) { + return _proposalVotes[proposalId].voteReceipt[account].hasOverriden; + } + + /** + * @dev Accessor to the internal vote counts. + */ + function proposalVotes( + uint256 proposalId + ) public view virtual returns (uint256 againstVotes, uint256 forVotes, uint256 abstainVotes) { + uint256[3] storage votes = _proposalVotes[proposalId].votes; + return (votes[uint8(VoteType.Against)], votes[uint8(VoteType.For)], votes[uint8(VoteType.Abstain)]); + } + + /** + * @dev See {Governor-_quorumReached}. + */ + function _quorumReached(uint256 proposalId) internal view virtual override returns (bool) { + uint256[3] storage votes = _proposalVotes[proposalId].votes; + return quorum(proposalSnapshot(proposalId)) <= votes[uint8(VoteType.For)] + votes[uint8(VoteType.Abstain)]; + } + + /** + * @dev See {Governor-_voteSucceeded}. In this module, the forVotes must be strictly over the againstVotes. + */ + function _voteSucceeded(uint256 proposalId) internal view virtual override returns (bool) { + uint256[3] storage votes = _proposalVotes[proposalId].votes; + return votes[uint8(VoteType.For)] > votes[uint8(VoteType.Against)]; + } + + /** + * @dev See {Governor-_countVote}. In this module, the support follows the `VoteType` enum (from Governor Bravo). + * + * NOTE: called by {Governor-_castVote} which emits the {IGovernor-VoteCast} (or {IGovernor-VoteCastWithParams}) + * event. + */ + function _countVote( + uint256 proposalId, + address account, + uint8 support, + uint256 totalWeight, + bytes memory /*params*/ + ) internal virtual override returns (uint256) { + ProposalVote storage proposalVote = _proposalVotes[proposalId]; + + if (support > uint8(VoteType.Abstain)) { + revert GovernorInvalidVoteType(); + } + + if (proposalVote.voteReceipt[account].casted != 0) { + revert GovernorAlreadyCastVote(account); + } + + totalWeight -= proposalVote.voteReceipt[account].overridenWeight; + proposalVote.votes[support] += totalWeight; + proposalVote.voteReceipt[account].casted = support + 1; + + return totalWeight; + } + + /// @dev Variant of {Governor-_countVote} that deals with vote overrides. + function _countOverride(uint256 proposalId, address account, uint8 support) internal virtual returns (uint256) { + ProposalVote storage proposalVote = _proposalVotes[proposalId]; + + if (support > uint8(VoteType.Abstain)) { + revert GovernorInvalidVoteType(); + } + + if (proposalVote.voteReceipt[account].hasOverriden) { + revert GovernorAlreadyOverridenVote(account); + } + + uint256 proposalSnapshot = proposalSnapshot(proposalId); + uint256 overridenWeight = VotesExtended(address(token())).getPastBalanceOf(account, proposalSnapshot); + address delegate = VotesExtended(address(token())).getPastDelegate(account, proposalSnapshot); + uint8 delegateCasted = proposalVote.voteReceipt[delegate].casted; + + proposalVote.voteReceipt[account].hasOverriden = true; + proposalVote.votes[support] += overridenWeight; + if (delegateCasted == 0) { + proposalVote.voteReceipt[delegate].overridenWeight += SafeCast.toUint208(overridenWeight); + } else { + uint8 delegateSupport = delegateCasted - 1; + proposalVote.votes[delegateSupport] -= overridenWeight; + emit VoteReduced(delegate, proposalId, delegateSupport, overridenWeight); + } + + return overridenWeight; + } + + /// @dev variant of {Governor-_castVote} that deals with vote overrides. + function _castOverride( + uint256 proposalId, + address account, + uint8 support, + string calldata reason + ) internal virtual returns (uint256) { + _validateStateBitmap(proposalId, _encodeStateBitmap(ProposalState.Active)); + + uint256 overridenWeight = _countOverride(proposalId, account, support); + + emit OverrideVoteCast(account, proposalId, support, overridenWeight, reason); + + _tallyUpdated(proposalId); + + return overridenWeight; + } + + /// @dev Public function for casting an override vote + function castOverrideVote( + uint256 proposalId, + uint8 support, + string calldata reason + ) public virtual returns (uint256) { + address voter = _msgSender(); + return _castOverride(proposalId, voter, support, reason); + } + + /// @dev Public function for casting an override vote using a voter's signature + function castOverrideVoteBySig( + uint256 proposalId, + uint8 support, + address voter, + string calldata reason, + bytes calldata signature + ) public virtual returns (uint256) { + bool valid = SignatureChecker.isValidSignatureNow( + voter, + _hashTypedDataV4( + keccak256( + abi.encode( + OVERRIDE_BALLOT_TYPEHASH, + proposalId, + support, + voter, + _useNonce(voter), + keccak256(bytes(reason)) + ) + ) + ), + signature + ); + + if (!valid) { + revert GovernorInvalidSignature(voter); + } + + return _castOverride(proposalId, voter, support, reason); + } +} diff --git a/contracts/governance/extensions/GovernorPreventLateQuorum.sol b/contracts/governance/extensions/GovernorPreventLateQuorum.sol index ff80af648db..eb93add8099 100644 --- a/contracts/governance/extensions/GovernorPreventLateQuorum.sol +++ b/contracts/governance/extensions/GovernorPreventLateQuorum.sol @@ -44,20 +44,12 @@ abstract contract GovernorPreventLateQuorum is Governor { } /** - * @dev Casts a vote and detects if it caused quorum to be reached, potentially extending the voting period. See - * {Governor-_castVote}. + * @dev Vote tally updated and detects if it caused quorum to be reached, potentially extending the voting period. * * May emit a {ProposalExtended} event. */ - function _castVote( - uint256 proposalId, - address account, - uint8 support, - string memory reason, - bytes memory params - ) internal virtual override returns (uint256) { - uint256 result = super._castVote(proposalId, account, support, reason, params); - + function _tallyUpdated(uint256 proposalId) internal virtual override { + super._tallyUpdated(proposalId); if (_extendedDeadlines[proposalId] == 0 && _quorumReached(proposalId)) { uint48 extendedDeadline = clock() + lateQuorumVoteExtension(); @@ -67,8 +59,6 @@ abstract contract GovernorPreventLateQuorum is Governor { _extendedDeadlines[proposalId] = extendedDeadline; } - - return result; } /** diff --git a/contracts/governance/utils/VotesExtended.sol b/contracts/governance/utils/VotesExtended.sol new file mode 100644 index 00000000000..62ddd5f7a2e --- /dev/null +++ b/contracts/governance/utils/VotesExtended.sol @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {Checkpoints} from "../../utils/structs/Checkpoints.sol"; +import {Votes} from "./Votes.sol"; +import {SafeCast} from "../../utils/math/SafeCast.sol"; + +/** + * @dev Extension of {Votes} that adds exposes checkpoints for delegations and balances. + */ +abstract contract VotesExtended is Votes { + using SafeCast for uint256; + using Checkpoints for Checkpoints.Trace160; + using Checkpoints for Checkpoints.Trace208; + + mapping(address delegatee => Checkpoints.Trace160) private _delegateCheckpoints; + mapping(address account => Checkpoints.Trace208) private _balanceOfCheckpoints; + + /** + * @dev Returns the delegate of an `account` at a specific moment in the past. If the `clock()` is + * configured to use block numbers, this will return the value at the end of the corresponding block. + * + * Requirements: + * + * - `timepoint` must be in the past. If operating using block numbers, the block must be already mined. + */ + function getPastDelegate(address account, uint256 timepoint) public view virtual returns (address) { + uint48 currentTimepoint = clock(); + if (timepoint >= currentTimepoint) { + revert ERC5805FutureLookup(timepoint, currentTimepoint); + } + return address(_delegateCheckpoints[account].upperLookupRecent(timepoint.toUint48())); + } + + /** + * @dev Returns the `balanceOf` of an `account` at a specific moment in the past. If the `clock()` is + * configured to use block numbers, this will return the value at the end of the corresponding block. + * + * Requirements: + * + * - `timepoint` must be in the past. If operating using block numbers, the block must be already mined. + */ + function getPastBalanceOf(address account, uint256 timepoint) public view virtual returns (uint256) { + uint48 currentTimepoint = clock(); + if (timepoint >= currentTimepoint) { + revert ERC5805FutureLookup(timepoint, currentTimepoint); + } + return _balanceOfCheckpoints[account].upperLookupRecent(timepoint.toUint48()); + } + + /// @inheritdoc Votes + function _delegate(address account, address delegatee) internal virtual override { + super._delegate(account, delegatee); + + _delegateCheckpoints[account].push(clock(), uint160(delegatee)); + } + + /// @inheritdoc Votes + function _transferVotingUnits(address from, address to, uint256 amount) internal virtual override { + super._transferVotingUnits(from, to, amount); + if (from != to) { + if (from != address(0)) { + _balanceOfCheckpoints[from].push(clock(), _getVotingUnits(from).toUint208()); + } + if (to != address(0)) { + _balanceOfCheckpoints[to].push(clock(), _getVotingUnits(to).toUint208()); + } + } + } +} diff --git a/contracts/mocks/VotesAdditionalCheckpointsMock.sol b/contracts/mocks/VotesAdditionalCheckpointsMock.sol new file mode 100644 index 00000000000..9c456190e97 --- /dev/null +++ b/contracts/mocks/VotesAdditionalCheckpointsMock.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {VotesExtended} from "../governance/utils/VotesExtended.sol"; + +abstract contract VotesExtendedMock is VotesExtended { + mapping(address voter => uint256) private _votingUnits; + + function getTotalSupply() public view returns (uint256) { + return _getTotalSupply(); + } + + function delegate(address account, address newDelegation) public { + return _delegate(account, newDelegation); + } + + function _getVotingUnits(address account) internal view override returns (uint256) { + return _votingUnits[account]; + } + + function _mint(address account, uint256 votes) internal { + _votingUnits[account] += votes; + _transferVotingUnits(address(0), account, votes); + } + + function _burn(address account, uint256 votes) internal { + _votingUnits[account] += votes; + _transferVotingUnits(account, address(0), votes); + } +} + +abstract contract VotesExtendedTimestampMock is VotesExtendedMock { + function clock() public view override returns (uint48) { + return uint48(block.timestamp); + } + + // solhint-disable-next-line func-name-mixedcase + function CLOCK_MODE() public view virtual override returns (string memory) { + return "mode=timestamp"; + } +} diff --git a/contracts/mocks/governance/GovernorCountingOverridableMock.sol b/contracts/mocks/governance/GovernorCountingOverridableMock.sol new file mode 100644 index 00000000000..bae09d933ad --- /dev/null +++ b/contracts/mocks/governance/GovernorCountingOverridableMock.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {Governor} from "../../governance/Governor.sol"; +import {GovernorSettings} from "../../governance/extensions/GovernorSettings.sol"; +import {GovernorVotesQuorumFraction} from "../../governance/extensions/GovernorVotesQuorumFraction.sol"; +import {GovernorCountingOverridable, VotesExtended} from "../../governance/extensions/GovernorCountingOverridable.sol"; + +abstract contract GovernorCountingOverridableMock is + GovernorSettings, + GovernorVotesQuorumFraction, + GovernorCountingOverridable +{ + function proposalThreshold() public view override(Governor, GovernorSettings) returns (uint256) { + return super.proposalThreshold(); + } +} diff --git a/contracts/mocks/governance/GovernorPreventLateQuorumMock.sol b/contracts/mocks/governance/GovernorPreventLateQuorumMock.sol index fde0863ce86..176976f9199 100644 --- a/contracts/mocks/governance/GovernorPreventLateQuorumMock.sol +++ b/contracts/mocks/governance/GovernorPreventLateQuorumMock.sol @@ -34,13 +34,7 @@ abstract contract GovernorPreventLateQuorumMock is return super.proposalThreshold(); } - function _castVote( - uint256 proposalId, - address account, - uint8 support, - string memory reason, - bytes memory params - ) internal override(Governor, GovernorPreventLateQuorum) returns (uint256) { - return super._castVote(proposalId, account, support, reason, params); + function _tallyUpdated(uint256 proposalId) internal override(Governor, GovernorPreventLateQuorum) { + super._tallyUpdated(proposalId); } } diff --git a/contracts/mocks/token/ERC20VotesAdditionalCheckpointsMock.sol b/contracts/mocks/token/ERC20VotesAdditionalCheckpointsMock.sol new file mode 100644 index 00000000000..39b3c654b17 --- /dev/null +++ b/contracts/mocks/token/ERC20VotesAdditionalCheckpointsMock.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {ERC20Votes} from "../../token/ERC20/extensions/ERC20Votes.sol"; +import {VotesExtended, Votes} from "../../governance/utils/VotesExtended.sol"; +import {SafeCast} from "../../utils/math/SafeCast.sol"; + +abstract contract ERC20VotesExtendedMock is ERC20Votes, VotesExtended { + function _delegate(address account, address delegatee) internal virtual override(Votes, VotesExtended) { + return super._delegate(account, delegatee); + } + + function _transferVotingUnits( + address from, + address to, + uint256 amount + ) internal virtual override(Votes, VotesExtended) { + return super._transferVotingUnits(from, to, amount); + } +} + +abstract contract ERC20VotesExtendedTimestampMock is ERC20VotesExtendedMock { + function clock() public view virtual override returns (uint48) { + return SafeCast.toUint48(block.timestamp); + } + + // solhint-disable-next-line func-name-mixedcase + function CLOCK_MODE() public view virtual override returns (string memory) { + return "mode=timestamp"; + } +} diff --git a/test/governance/extensions/GovernorCountingOverridable.test.js b/test/governance/extensions/GovernorCountingOverridable.test.js new file mode 100644 index 00000000000..6a54009df41 --- /dev/null +++ b/test/governance/extensions/GovernorCountingOverridable.test.js @@ -0,0 +1,344 @@ +const { ethers } = require('hardhat'); +const { expect } = require('chai'); +const { loadFixture, mine } = require('@nomicfoundation/hardhat-network-helpers'); + +const { GovernorHelper } = require('../../helpers/governance'); +const { getDomain, OverrideBallot } = require('../../helpers/eip712'); +const { VoteType } = require('../../helpers/enums'); + +const TOKENS = [ + { Token: '$ERC20VotesExtendedMock', mode: 'blocknumber' }, + // { Token: '$ERC20VotesExtendedTimestampMock', mode: 'timestamp' }, +]; + +const name = 'Override Governor'; +const version = '1'; +const tokenName = 'MockToken'; +const tokenSymbol = 'MTKN'; +const tokenSupply = ethers.parseEther('100'); +const votingDelay = 4n; +const votingPeriod = 16n; +const value = ethers.parseEther('1'); + +const signBallot = account => (contract, message) => + getDomain(contract).then(domain => account.signTypedData(domain, { OverrideBallot }, message)); + +describe('GovernorCountingOverridable', function () { + for (const { Token, mode } of TOKENS) { + const fixture = async () => { + const [owner, proposer, voter1, voter2, voter3, voter4, other] = await ethers.getSigners(); + const receiver = await ethers.deployContract('CallReceiverMock'); + + const token = await ethers.deployContract(Token, [tokenName, tokenSymbol, tokenName, version]); + const mock = await ethers.deployContract('$GovernorCountingOverridableMock', [ + name, // name + votingDelay, // initialVotingDelay + votingPeriod, // initialVotingPeriod + 0n, // initialProposalThreshold + token, // tokenAddress + 10n, // quorumNumeratorValue + ]); + + await owner.sendTransaction({ to: mock, value }); + await token.$_mint(owner, tokenSupply); + + const helper = new GovernorHelper(mock, mode); + await helper.connect(owner).delegate({ token, to: voter1, value: ethers.parseEther('10') }); + await helper.connect(owner).delegate({ token, to: voter2, value: ethers.parseEther('7') }); + await helper.connect(owner).delegate({ token, to: voter3, value: ethers.parseEther('5') }); + await helper.connect(owner).delegate({ token, to: voter4, value: ethers.parseEther('2') }); + + return { owner, proposer, voter1, voter2, voter3, voter4, other, receiver, token, mock, helper }; + }; + + describe(`using ${Token}`, function () { + beforeEach(async function () { + Object.assign(this, await loadFixture(fixture)); + + // default proposal + this.proposal = this.helper.setProposal( + [ + { + target: this.receiver.target, + value, + data: this.receiver.interface.encodeFunctionData('mockFunction'), + }, + ], + '', + ); + }); + + it('deployment check', async function () { + expect(await this.mock.name()).to.equal(name); + expect(await this.mock.token()).to.equal(this.token); + expect(await this.mock.votingDelay()).to.equal(votingDelay); + expect(await this.mock.votingPeriod()).to.equal(votingPeriod); + expect(await this.mock.COUNTING_MODE()).to.equal('support=bravo,override&quorum=for,abstain&overridable=true'); + }); + + it('nominal is unaffected', async function () { + await this.helper.connect(this.proposer).propose(); + await this.helper.waitForSnapshot(); + await this.helper.connect(this.voter1).vote({ support: VoteType.For, reason: 'This is nice' }); + await this.helper.connect(this.voter2).vote({ support: VoteType.For }); + await this.helper.connect(this.voter3).vote({ support: VoteType.Against }); + await this.helper.connect(this.voter4).vote({ support: VoteType.Abstain }); + await this.helper.waitForDeadline(); + await this.helper.execute(); + + expect(await this.mock.hasVoted(this.proposal.id, this.owner)).to.be.false; + expect(await this.mock.hasVoted(this.proposal.id, this.voter1)).to.be.true; + expect(await this.mock.hasVoted(this.proposal.id, this.voter2)).to.be.true; + expect(await ethers.provider.getBalance(this.mock)).to.equal(0n); + expect(await ethers.provider.getBalance(this.receiver)).to.equal(value); + }); + + describe('cast override vote', async function () { + beforeEach(async function () { + // user 1 -(delegate 10 tokens)-> user 2 + // user 2 -(delegate 7 tokens)-> user 2 + // user 3 -(delegate 5 tokens)-> user 1 + // user 4 -(delegate 2 tokens)-> user 2 + await this.token.connect(this.voter1).delegate(this.voter2); + await this.token.connect(this.voter3).delegate(this.voter1); + await this.token.connect(this.voter4).delegate(this.voter2); + await mine(); + + await this.helper.connect(this.proposer).propose(); + await this.helper.waitForSnapshot(); + }); + + it('override after delegate vote', async function () { + expect(await this.mock.hasVoted(this.helper.id, this.voter1)).to.be.false; + expect(await this.mock.hasVoted(this.helper.id, this.voter2)).to.be.false; + expect(await this.mock.hasVoted(this.helper.id, this.voter3)).to.be.false; + expect(await this.mock.hasVoted(this.helper.id, this.voter4)).to.be.false; + expect(await this.mock.hasVotedOverride(this.helper.id, this.voter1)).to.be.false; + expect(await this.mock.hasVotedOverride(this.helper.id, this.voter2)).to.be.false; + expect(await this.mock.hasVotedOverride(this.helper.id, this.voter3)).to.be.false; + expect(await this.mock.hasVotedOverride(this.helper.id, this.voter4)).to.be.false; + + // user 2 votes + + await expect(this.helper.connect(this.voter2).vote({ support: VoteType.For })) + .to.emit(this.mock, 'VoteCast') + .withArgs(this.voter2, this.helper.id, VoteType.For, ethers.parseEther('19'), ''); // 10 + 7 + 2 + + expect(await this.mock.proposalVotes(this.helper.id)).to.deep.eq( + [0, 19, 0].map(x => ethers.parseEther(x.toString())), + ); + expect(await this.mock.hasVoted(this.helper.id, this.voter2)).to.be.true; + expect(await this.mock.hasVotedOverride(this.helper.id, this.voter2)).to.be.false; + + // user 1 overrides after user 2 votes + + const reason = "disagree with user 2's decision"; + await expect(this.mock.connect(this.voter1).castOverrideVote(this.helper.id, VoteType.Against, reason)) + .to.emit(this.mock, 'OverrideVoteCast') + .withArgs(this.voter1, this.helper.id, VoteType.Against, ethers.parseEther('10'), reason) + .to.emit(this.mock, 'VoteReduced') + .withArgs(this.voter2, this.helper.id, VoteType.For, ethers.parseEther('10')); + + expect(await this.mock.proposalVotes(this.helper.id)).to.deep.eq( + [10, 9, 0].map(x => ethers.parseEther(x.toString())), + ); + expect(await this.mock.hasVoted(this.helper.id, this.voter1)).to.be.false; + expect(await this.mock.hasVotedOverride(this.helper.id, this.voter1)).to.be.true; + }); + + it('override before delegate vote', async function () { + expect(await this.mock.hasVoted(this.helper.id, this.voter1)).to.be.false; + expect(await this.mock.hasVoted(this.helper.id, this.voter2)).to.be.false; + expect(await this.mock.hasVoted(this.helper.id, this.voter3)).to.be.false; + expect(await this.mock.hasVoted(this.helper.id, this.voter4)).to.be.false; + expect(await this.mock.hasVotedOverride(this.helper.id, this.voter1)).to.be.false; + expect(await this.mock.hasVotedOverride(this.helper.id, this.voter2)).to.be.false; + expect(await this.mock.hasVotedOverride(this.helper.id, this.voter3)).to.be.false; + expect(await this.mock.hasVotedOverride(this.helper.id, this.voter4)).to.be.false; + + // user 1 overrides before user 2 votes + + const reason = 'voter 2 is not voting'; + await expect(this.mock.connect(this.voter1).castOverrideVote(this.helper.id, VoteType.Against, reason)) + .to.emit(this.mock, 'OverrideVoteCast') + .withArgs(this.voter1, this.helper.id, VoteType.Against, ethers.parseEther('10'), reason) + .to.not.emit(this.mock, 'VoteReduced'); + + expect(await this.mock.proposalVotes(this.helper.id)).to.deep.eq( + [10, 0, 0].map(x => ethers.parseEther(x.toString())), + ); + expect(await this.mock.hasVoted(this.helper.id, this.voter1)).to.be.false; + expect(await this.mock.hasVotedOverride(this.helper.id, this.voter1)).to.be.true; + + // user 2 votes + + await expect(this.helper.connect(this.voter2).vote({ support: VoteType.For })) + .to.emit(this.mock, 'VoteCast') + .withArgs(this.voter2, this.helper.id, VoteType.For, ethers.parseEther('9'), ''); // 7 + 2 + + expect(await this.mock.proposalVotes(this.helper.id)).to.deep.eq( + [10, 9, 0].map(x => ethers.parseEther(x.toString())), + ); + expect(await this.mock.hasVoted(this.helper.id, this.voter2)).to.be.true; + expect(await this.mock.hasVotedOverride(this.helper.id, this.voter2)).to.be.false; + }); + + it('override before and after delegate vote', async function () { + expect(await this.mock.hasVoted(this.helper.id, this.voter1)).to.be.false; + expect(await this.mock.hasVoted(this.helper.id, this.voter2)).to.be.false; + expect(await this.mock.hasVoted(this.helper.id, this.voter3)).to.be.false; + expect(await this.mock.hasVoted(this.helper.id, this.voter4)).to.be.false; + expect(await this.mock.hasVotedOverride(this.helper.id, this.voter1)).to.be.false; + expect(await this.mock.hasVotedOverride(this.helper.id, this.voter2)).to.be.false; + expect(await this.mock.hasVotedOverride(this.helper.id, this.voter3)).to.be.false; + expect(await this.mock.hasVotedOverride(this.helper.id, this.voter4)).to.be.false; + + // user 1 overrides before user 2 votes + + const reason = 'voter 2 is not voting'; + await expect(this.mock.connect(this.voter1).castOverrideVote(this.helper.id, VoteType.Against, reason)) + .to.emit(this.mock, 'OverrideVoteCast') + .withArgs(this.voter1, this.helper.id, VoteType.Against, ethers.parseEther('10'), reason) + .to.not.emit(this.mock, 'VoteReduced'); + + expect(await this.mock.proposalVotes(this.helper.id)).to.deep.eq( + [10, 0, 0].map(x => ethers.parseEther(x.toString())), + ); + expect(await this.mock.hasVoted(this.helper.id, this.voter1)).to.be.false; + expect(await this.mock.hasVotedOverride(this.helper.id, this.voter1)).to.be.true; + + // user 2 votes + + await expect(this.helper.connect(this.voter2).vote({ support: VoteType.For })) + .to.emit(this.mock, 'VoteCast') + .withArgs(this.voter2, this.helper.id, VoteType.For, ethers.parseEther('9'), ''); // 7 + 2 + + expect(await this.mock.proposalVotes(this.helper.id)).to.deep.eq( + [10, 9, 0].map(x => ethers.parseEther(x.toString())), + ); + expect(await this.mock.hasVoted(this.helper.id, this.voter2)).to.be.true; + expect(await this.mock.hasVotedOverride(this.helper.id, this.voter2)).to.be.false; + + // User 4 overrides after user 2 votes + + const reason2 = "disagree with user 2's decision"; + await expect(this.mock.connect(this.voter4).castOverrideVote(this.helper.id, VoteType.Abstain, reason2)) + .to.emit(this.mock, 'OverrideVoteCast') + .withArgs(this.voter4, this.helper.id, VoteType.Abstain, ethers.parseEther('2'), reason2) + .to.emit(this.mock, 'VoteReduced') + .withArgs(this.voter2, this.helper.id, VoteType.For, ethers.parseEther('2')); + + expect(await this.mock.proposalVotes(this.helper.id)).to.deep.eq( + [10, 7, 2].map(x => ethers.parseEther(x.toString())), + ); + expect(await this.mock.hasVoted(this.helper.id, this.voter4)).to.be.false; + expect(await this.mock.hasVotedOverride(this.helper.id, this.voter4)).to.be.true; + }); + + it('vote (with delegated balance) and override (with self balance) are independent', async function () { + expect(await this.mock.proposalVotes(this.helper.id)).to.deep.eq( + [0, 0, 0].map(x => ethers.parseEther(x.toString())), + ); + expect(await this.mock.hasVoted(this.helper.id, this.voter1)).to.be.false; + expect(await this.mock.hasVotedOverride(this.helper.id, this.voter1)).to.be.false; + + // user 1 votes with delegated weight from user 3 + await expect(this.mock.connect(this.voter1).castVote(this.helper.id, VoteType.For)) + .to.emit(this.mock, 'VoteCast') + .withArgs(this.voter1, this.helper.id, VoteType.For, ethers.parseEther('5'), ''); + + // user 1 cast an override vote with its own balance (delegated to user 2) + await expect(this.mock.connect(this.voter1).castOverrideVote(this.helper.id, VoteType.Against, '')) + .to.emit(this.mock, 'OverrideVoteCast') + .withArgs(this.voter1, this.helper.id, VoteType.Against, ethers.parseEther('10'), ''); + + expect(await this.mock.proposalVotes(this.helper.id)).to.deep.eq( + [10, 5, 0].map(x => ethers.parseEther(x.toString())), + ); + expect(await this.mock.hasVoted(this.helper.id, this.voter1)).to.be.true; + expect(await this.mock.hasVotedOverride(this.helper.id, this.voter1)).to.be.true; + }); + + it('can not override vote twice', async function () { + await expect(this.mock.connect(this.voter1).castOverrideVote(this.helper.id, VoteType.Against, '')) + .to.emit(this.mock, 'OverrideVoteCast') + .withArgs(this.voter1, this.helper.id, VoteType.Against, ethers.parseEther('10'), ''); + await expect(this.mock.connect(this.voter1).castOverrideVote(this.helper.id, VoteType.Abstain, '')) + .to.be.revertedWithCustomError(this.mock, 'GovernorAlreadyOverridenVote') + .withArgs(this.voter1.address); + }); + + it('can not vote twice', async function () { + await expect(this.mock.connect(this.voter1).castVote(this.helper.id, VoteType.Against)); + await expect(this.mock.connect(this.voter1).castVote(this.helper.id, VoteType.Abstain)) + .to.be.revertedWithCustomError(this.mock, 'GovernorAlreadyCastVote') + .withArgs(this.voter1.address); + }); + + describe('invalid vote type', function () { + it('override vote', async function () { + await expect( + this.mock.connect(this.voter1).castOverrideVote(this.helper.id, 3, ''), + ).to.be.revertedWithCustomError(this.mock, 'GovernorInvalidVoteType'); + }); + + it('traditional vote', async function () { + await expect(this.mock.connect(this.voter1).castVote(this.helper.id, 3)).to.be.revertedWithCustomError( + this.mock, + 'GovernorInvalidVoteType', + ); + }); + }); + + describe('by signature', function () { + it('EOA signature', async function () { + const nonce = await this.mock.nonces(this.voter1); + + await expect( + this.helper.overrideVote({ + support: VoteType.For, + voter: this.voter1.address, + nonce, + signature: signBallot(this.voter1), + }), + ) + .to.emit(this.mock, 'OverrideVoteCast') + .withArgs(this.voter1, this.helper.id, VoteType.For, ethers.parseEther('10'), ''); + + expect(await this.mock.hasVotedOverride(this.proposal.id, this.voter1)).to.be.true; + }); + + it('revert if signature does not match signer', async function () { + const nonce = await this.mock.nonces(this.voter1); + + const voteParams = { + support: VoteType.For, + voter: this.voter2.address, + nonce, + signature: signBallot(this.voter1), + }; + + await expect(this.helper.overrideVote(voteParams)) + .to.be.revertedWithCustomError(this.mock, 'GovernorInvalidSignature') + .withArgs(voteParams.voter); + }); + + it('revert if vote nonce is incorrect', async function () { + const nonce = await this.mock.nonces(this.voter1); + + const voteParams = { + support: VoteType.For, + voter: this.voter1.address, + nonce: nonce + 1n, + signature: signBallot(this.voter1), + }; + + await expect(this.helper.overrideVote(voteParams)) + .to.be.revertedWithCustomError(this.mock, 'GovernorInvalidSignature') + .withArgs(voteParams.voter); + }); + }); + }); + }); + } +}); diff --git a/test/governance/utils/VotesAdditionalCheckpoints.test.js b/test/governance/utils/VotesAdditionalCheckpoints.test.js new file mode 100644 index 00000000000..4a66ef2748c --- /dev/null +++ b/test/governance/utils/VotesAdditionalCheckpoints.test.js @@ -0,0 +1,152 @@ +const { ethers } = require('hardhat'); +const { expect } = require('chai'); +const { loadFixture, mine } = require('@nomicfoundation/hardhat-network-helpers'); + +const { sum } = require('../../helpers/math'); +const { zip } = require('../../helpers/iterate'); +const time = require('../../helpers/time'); + +const { shouldBehaveLikeVotes } = require('./Votes.behavior'); + +const MODES = { + blocknumber: '$VotesExtendedMock', + timestamp: '$VotesExtendedTimestampMock', +}; + +const AMOUNTS = [ethers.parseEther('10000000'), 10n, 20n]; + +describe('VotesExtended', function () { + for (const [mode, artifact] of Object.entries(MODES)) { + const fixture = async () => { + const accounts = await ethers.getSigners(); + + const amounts = Object.fromEntries( + zip( + accounts.slice(0, AMOUNTS.length).map(({ address }) => address), + AMOUNTS, + ), + ); + + const name = 'Override Votes'; + const version = '1'; + const votes = await ethers.deployContract(artifact, [name, version]); + + return { accounts, amounts, votes, name, version }; + }; + + describe(`vote with ${mode}`, function () { + beforeEach(async function () { + Object.assign(this, await loadFixture(fixture)); + }); + + shouldBehaveLikeVotes(AMOUNTS, { mode, fungible: true }); + + it('starts with zero votes', async function () { + expect(await this.votes.getTotalSupply()).to.equal(0n); + }); + + describe('performs voting operations', function () { + beforeEach(async function () { + this.txs = []; + for (const [account, amount] of Object.entries(this.amounts)) { + this.txs.push(await this.votes.$_mint(account, amount)); + } + }); + + it('reverts if block number >= current block', async function () { + const lastTxTimepoint = await time.clockFromReceipt[mode](this.txs.at(-1)); + const clock = await this.votes.clock(); + await expect(this.votes.getPastTotalSupply(lastTxTimepoint)) + .to.be.revertedWithCustomError(this.votes, 'ERC5805FutureLookup') + .withArgs(lastTxTimepoint, clock); + }); + + it('delegates', async function () { + expect(await this.votes.getVotes(this.accounts[0])).to.equal(0n); + expect(await this.votes.getVotes(this.accounts[1])).to.equal(0n); + expect(await this.votes.delegates(this.accounts[0])).to.equal(ethers.ZeroAddress); + expect(await this.votes.delegates(this.accounts[1])).to.equal(ethers.ZeroAddress); + + await this.votes.delegate(this.accounts[0], ethers.Typed.address(this.accounts[0])); + + expect(await this.votes.getVotes(this.accounts[0])).to.equal(this.amounts[this.accounts[0].address]); + expect(await this.votes.getVotes(this.accounts[1])).to.equal(0n); + expect(await this.votes.delegates(this.accounts[0])).to.equal(this.accounts[0]); + expect(await this.votes.delegates(this.accounts[1])).to.equal(ethers.ZeroAddress); + + await this.votes.delegate(this.accounts[1], ethers.Typed.address(this.accounts[0])); + + expect(await this.votes.getVotes(this.accounts[0])).to.equal( + this.amounts[this.accounts[0].address] + this.amounts[this.accounts[1].address], + ); + expect(await this.votes.getVotes(this.accounts[1])).to.equal(0n); + expect(await this.votes.delegates(this.accounts[0])).to.equal(this.accounts[0]); + expect(await this.votes.delegates(this.accounts[1])).to.equal(this.accounts[0]); + }); + + it('cross delegates', async function () { + await this.votes.delegate(this.accounts[0], ethers.Typed.address(this.accounts[1])); + await this.votes.delegate(this.accounts[1], ethers.Typed.address(this.accounts[0])); + + expect(await this.votes.getVotes(this.accounts[0])).to.equal(this.amounts[this.accounts[1].address]); + expect(await this.votes.getVotes(this.accounts[1])).to.equal(this.amounts[this.accounts[0].address]); + }); + + it('returns total amount of votes', async function () { + const totalSupply = sum(...Object.values(this.amounts)); + expect(await this.votes.getTotalSupply()).to.equal(totalSupply); + }); + }); + }); + + describe(`checkpoint delegates with ${mode}`, function () { + beforeEach(async function () { + Object.assign(this, await loadFixture(fixture)); + }); + + it('checkpoint delegates', async function () { + const tx = await this.votes.delegate(this.accounts[0], ethers.Typed.address(this.accounts[1])); + const timepoint = await time.clockFromReceipt[mode](tx); + await mine(2); + + expect(await this.votes.getPastDelegate(this.accounts[0], timepoint - 1n)).to.equal(ethers.ZeroAddress); + expect(await this.votes.getPastDelegate(this.accounts[0], timepoint)).to.equal(this.accounts[1].address); + expect(await this.votes.getPastDelegate(this.accounts[0], timepoint + 1n)).to.equal(this.accounts[1].address); + }); + + it('reverts if current timepoint <= timepoint', async function () { + const tx = await this.votes.delegate(this.accounts[0], ethers.Typed.address(this.accounts[1])); + const timepoint = await time.clockFromReceipt[mode](tx); + + await expect(this.votes.getPastDelegate(this.accounts[0], timepoint + 1n)) + .to.be.revertedWithCustomError(this.votes, 'ERC5805FutureLookup') + .withArgs(timepoint + 1n, timepoint); + }); + }); + + describe(`checkpoint balances with ${mode}`, function () { + beforeEach(async function () { + Object.assign(this, await loadFixture(fixture)); + }); + + it('checkpoint balances', async function () { + const tx = await this.votes.$_mint(this.accounts[0].address, 100n); + const timepoint = await time.clockFromReceipt[mode](tx); + await mine(2); + + expect(await this.votes.getPastBalanceOf(this.accounts[0].address, timepoint - 1n)).to.equal(0n); + expect(await this.votes.getPastBalanceOf(this.accounts[0].address, timepoint)).to.equal(100n); + expect(await this.votes.getPastBalanceOf(this.accounts[0].address, timepoint + 1n)).to.equal(100n); + }); + + it('reverts if current timepoint <= timepoint', async function () { + const tx = await this.votes.$_mint(this.accounts[0].address, 100n); + const timepoint = await time.clockFromReceipt[mode](tx); + + await expect(this.votes.getPastBalanceOf(this.accounts[0], timepoint + 1n)) + .to.be.revertedWithCustomError(this.votes, 'ERC5805FutureLookup') + .withArgs(timepoint + 1n, timepoint); + }); + }); + } +}); diff --git a/test/helpers/eip712-types.js b/test/helpers/eip712-types.js index b2b6ccf837b..d04969e442b 100644 --- a/test/helpers/eip712-types.js +++ b/test/helpers/eip712-types.js @@ -32,6 +32,13 @@ module.exports = mapValues( reason: 'string', params: 'bytes', }, + OverrideBallot: { + proposalId: 'uint256', + support: 'uint8', + voter: 'address', + nonce: 'uint256', + reason: 'string', + }, Delegation: { delegatee: 'address', nonce: 'uint256', diff --git a/test/helpers/governance.js b/test/helpers/governance.js index dce5927b730..540967af49d 100644 --- a/test/helpers/governance.js +++ b/test/helpers/governance.js @@ -128,6 +128,23 @@ class GovernorHelper { return await this.governor[method](...args); } + async overrideVote(vote = {}) { + let method = 'castOverrideVote'; + let args = [this.id, vote.support]; + + vote.reason = vote.reason ?? ''; + + if (vote.signature) { + let message = this.forgeMessage(vote); + message.reason = message.reason ?? ''; + const sign = await vote.signature(this.governor, message); + method = 'castOverrideVoteBySig'; + args.push(vote.voter, vote.reason ?? '', sign); + } + + return await this.governor[method](...args); + } + /// Clock helpers async waitForSnapshot(offset = 0n) { const timepoint = await this.governor.proposalSnapshot(this.id); From 2a83a217afff9acb874478be9eb83cb58d7eafbe Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Fri, 18 Oct 2024 14:45:36 +0200 Subject: [PATCH 33/84] GovernorCountingOverridable testing with timestamp (#5269) --- ...VotesAdditionalCheckpointsMock.sol => VotesExtendedMock.sol} | 0 test/governance/extensions/GovernorCountingOverridable.test.js | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename contracts/mocks/{VotesAdditionalCheckpointsMock.sol => VotesExtendedMock.sol} (100%) diff --git a/contracts/mocks/VotesAdditionalCheckpointsMock.sol b/contracts/mocks/VotesExtendedMock.sol similarity index 100% rename from contracts/mocks/VotesAdditionalCheckpointsMock.sol rename to contracts/mocks/VotesExtendedMock.sol diff --git a/test/governance/extensions/GovernorCountingOverridable.test.js b/test/governance/extensions/GovernorCountingOverridable.test.js index 6a54009df41..92e86f45019 100644 --- a/test/governance/extensions/GovernorCountingOverridable.test.js +++ b/test/governance/extensions/GovernorCountingOverridable.test.js @@ -8,7 +8,7 @@ const { VoteType } = require('../../helpers/enums'); const TOKENS = [ { Token: '$ERC20VotesExtendedMock', mode: 'blocknumber' }, - // { Token: '$ERC20VotesExtendedTimestampMock', mode: 'timestamp' }, + { Token: '$ERC20VotesExtendedTimestampMock', mode: 'timestamp' }, ]; const name = 'Override Governor'; From f989fff93168606c726bc5e831ef50dd6e543f45 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Fri, 18 Oct 2024 15:07:34 +0200 Subject: [PATCH 34/84] Remove missed changeset from 5.1-rc.1 (#5270) --- .changeset/yellow-tables-sell.md | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 .changeset/yellow-tables-sell.md diff --git a/.changeset/yellow-tables-sell.md b/.changeset/yellow-tables-sell.md deleted file mode 100644 index f8cdc8d306b..00000000000 --- a/.changeset/yellow-tables-sell.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`SafeERC20`: Document risks of `safeIncreaseAllowance` and `safeDecreaseAllowance` when associated with ERC-7674. From 5bb3f3e788c6b2c806d562ef083b438354f969d7 Mon Sep 17 00:00:00 2001 From: Abhishek Sharma <34743587+Shaykoo@users.noreply.github.com> Date: Sat, 19 Oct 2024 19:16:09 +0700 Subject: [PATCH 35/84] Simplify content in security documentation (#5233) Co-authored-by: Hadrien Croubois Co-authored-by: cairo --- docs/modules/ROOT/pages/extending-contracts.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/modules/ROOT/pages/extending-contracts.adoc b/docs/modules/ROOT/pages/extending-contracts.adoc index 1cdc0d7814f..8ff4101f220 100644 --- a/docs/modules/ROOT/pages/extending-contracts.adoc +++ b/docs/modules/ROOT/pages/extending-contracts.adoc @@ -46,6 +46,6 @@ NOTE: The same rule is implemented and extended in xref:api:access.adoc#AccessCo The maintainers of OpenZeppelin Contracts are mainly concerned with the correctness and security of the code as published in the library, and the combinations of base contracts with the official extensions from the library. -Custom overrides, and those of hooks in particular, may break some important assumptions and introduce vulnerabilities in otherwise secure code. While we try to ensure the contracts remain secure in the face of a wide range of potential customizations, this is done in a best-effort manner. While we try to document all important assumptions, this should not be relied upon. Custom overrides should be carefully reviewed and checked against the source code of the contract they are customizing so as to fully understand their impact and guarantee their security. +Custom overrides, especially to hooks, can disrupt important assumptions and may introduce security risks in the code that was previously secure. While we try to ensure the contracts remain secure in the face of a wide range of potential customizations, this is done in a best-effort manner. While we try to document all important assumptions, this should not be relied upon. Custom overrides should be carefully reviewed and checked against the source code of the contract they are customizing to fully understand their impact and guarantee their security. The way functions interact internally should not be assumed to stay stable across releases of the library. For example, a function that is used in one context in a particular release may not be used in the same context in the next release. Contracts that override functions should revalidate their assumptions when updating the version of OpenZeppelin Contracts they are built on. From a4dc928a93a887ba6baf69f821e5aee764516bec Mon Sep 17 00:00:00 2001 From: Maxim Tiron Date: Mon, 21 Oct 2024 14:39:21 +0300 Subject: [PATCH 36/84] ERC20: optimized gas costs in `_spendAllowance` (#5271) --- contracts/token/ERC20/ERC20.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/token/ERC20/ERC20.sol b/contracts/token/ERC20/ERC20.sol index 0b707604c0c..6a9865e6aef 100644 --- a/contracts/token/ERC20/ERC20.sol +++ b/contracts/token/ERC20/ERC20.sol @@ -300,7 +300,7 @@ abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors { */ function _spendAllowance(address owner, address spender, uint256 value) internal virtual { uint256 currentAllowance = allowance(owner, spender); - if (currentAllowance != type(uint256).max) { + if (currentAllowance < type(uint256).max) { if (currentAllowance < value) { revert ERC20InsufficientAllowance(spender, currentAllowance, value); } From c12cf86e0d85e089a8d3b2b9e733c2194ff89d45 Mon Sep 17 00:00:00 2001 From: cairo Date: Mon, 21 Oct 2024 13:44:22 +0200 Subject: [PATCH 37/84] Fuzz tampered tests for `ERC2771Forwarder` (#5258) Co-authored-by: Hadrien Croubois --- test/metatx/ERC2771Forwarder.t.sol | 270 +++++++++++++++++++-------- test/metatx/ERC2771Forwarder.test.js | 77 -------- 2 files changed, 192 insertions(+), 155 deletions(-) diff --git a/test/metatx/ERC2771Forwarder.t.sol b/test/metatx/ERC2771Forwarder.t.sol index d69b4750a39..e6baac6f030 100644 --- a/test/metatx/ERC2771Forwarder.t.sol +++ b/test/metatx/ERC2771Forwarder.t.sol @@ -5,21 +5,24 @@ pragma solidity ^0.8.20; import {Test} from "forge-std/Test.sol"; import {ERC2771Forwarder} from "@openzeppelin/contracts/metatx/ERC2771Forwarder.sol"; import {CallReceiverMockTrustingForwarder, CallReceiverMock} from "@openzeppelin/contracts/mocks/CallReceiverMock.sol"; - -struct ForwardRequest { - address from; - address to; - uint256 value; - uint256 gas; - uint256 nonce; - uint48 deadline; - bytes data; +import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; + +enum TamperType { + FROM, + TO, + VALUE, + DATA, + SIGNATURE } contract ERC2771ForwarderMock is ERC2771Forwarder { constructor(string memory name) ERC2771Forwarder(name) {} - function structHash(ForwardRequest calldata request) external view returns (bytes32) { + function forwardRequestStructHash( + ERC2771Forwarder.ForwardRequestData calldata request, + uint256 nonce + ) external view returns (bytes32) { return _hashTypedDataV4( keccak256( @@ -29,7 +32,7 @@ contract ERC2771ForwarderMock is ERC2771Forwarder { request.to, request.value, request.gas, - request.nonce, + nonce, request.deadline, keccak256(request.data) ) @@ -39,127 +42,238 @@ contract ERC2771ForwarderMock is ERC2771Forwarder { } contract ERC2771ForwarderTest is Test { + using ECDSA for bytes32; + ERC2771ForwarderMock internal _erc2771Forwarder; CallReceiverMockTrustingForwarder internal _receiver; - uint256 internal _signerPrivateKey; - uint256 internal _relayerPrivateKey; - - address internal _signer; - address internal _relayer; + uint256 internal _signerPrivateKey = 0xA11CE; + address internal _signer = vm.addr(_signerPrivateKey); uint256 internal constant _MAX_ETHER = 10_000_000; // To avoid overflow function setUp() public { _erc2771Forwarder = new ERC2771ForwarderMock("ERC2771Forwarder"); _receiver = new CallReceiverMockTrustingForwarder(address(_erc2771Forwarder)); + } - _signerPrivateKey = 0xA11CE; - _relayerPrivateKey = 0xB0B; - - _signer = vm.addr(_signerPrivateKey); - _relayer = vm.addr(_relayerPrivateKey); + // Forge a new ForwardRequestData + function _forgeRequestData() private view returns (ERC2771Forwarder.ForwardRequestData memory) { + return + _forgeRequestData({ + value: 0, + deadline: uint48(block.timestamp + 1), + data: abi.encodeCall(CallReceiverMock.mockFunction, ()) + }); } function _forgeRequestData( uint256 value, - uint256 nonce, uint48 deadline, bytes memory data ) private view returns (ERC2771Forwarder.ForwardRequestData memory) { - ForwardRequest memory request = ForwardRequest({ - from: _signer, - to: address(_receiver), - value: value, - gas: 30000, - nonce: nonce, - deadline: deadline, - data: data - }); - - bytes32 digest = _erc2771Forwarder.structHash(request); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(_signerPrivateKey, digest); - bytes memory signature = abi.encodePacked(r, s, v); - return ERC2771Forwarder.ForwardRequestData({ - from: request.from, - to: request.to, - value: request.value, - gas: request.gas, - deadline: request.deadline, - data: request.data, - signature: signature + from: _signer, + to: address(_receiver), + value: value, + gas: 30000, + deadline: deadline, + data: data, + signature: "" }); } - function testExecuteAvoidsETHStuck(uint256 initialBalance, uint256 value, bool targetReverts) public { - initialBalance = bound(initialBalance, 0, _MAX_ETHER); - value = bound(value, 0, _MAX_ETHER); + // Sign a ForwardRequestData (in place) for a given nonce. Also returns it for convenience. + function _signRequestData( + ERC2771Forwarder.ForwardRequestData memory request, + uint256 nonce + ) private view returns (ERC2771Forwarder.ForwardRequestData memory) { + bytes32 digest = _erc2771Forwarder.forwardRequestStructHash(request, nonce); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(_signerPrivateKey, digest); + request.signature = abi.encodePacked(r, s, v); + return request; + } - vm.deal(address(_erc2771Forwarder), initialBalance); + // Tamper a ForwardRequestData (in place). Also returns it for convenience. + function _tamperRequestData( + ERC2771Forwarder.ForwardRequestData memory request, + TamperType tamper + ) private returns (ERC2771Forwarder.ForwardRequestData memory) { + if (tamper == TamperType.FROM) request.from = vm.randomAddress(); + else if (tamper == TamperType.TO) request.to = vm.randomAddress(); + else if (tamper == TamperType.VALUE) request.value = vm.randomUint(); + else if (tamper == TamperType.DATA) request.data = vm.randomBytes(4); + else if (tamper == TamperType.SIGNATURE) request.signature = vm.randomBytes(65); + + return request; + } - uint256 nonce = _erc2771Forwarder.nonces(_signer); + // Predict the revert error for a tampered request, and expect it is emitted. + function _tamperedExpectRevert( + ERC2771Forwarder.ForwardRequestData memory request, + TamperType tamper, + uint256 nonce + ) private returns (ERC2771Forwarder.ForwardRequestData memory) { + if (tamper == TamperType.FROM) nonce = _erc2771Forwarder.nonces(request.from); + + // predict revert + if (tamper == TamperType.TO) { + vm.expectRevert( + abi.encodeWithSelector( + ERC2771Forwarder.ERC2771UntrustfulTarget.selector, + request.to, + address(_erc2771Forwarder) + ) + ); + } else { + (address recovered, , ) = _erc2771Forwarder.forwardRequestStructHash(request, nonce).tryRecover( + request.signature + ); + vm.expectRevert( + abi.encodeWithSelector(ERC2771Forwarder.ERC2771ForwarderInvalidSigner.selector, recovered, request.from) + ); + } + return request; + } - vm.deal(address(this), value); + function testExecuteAvoidsETHStuck(uint256 initialBalance, uint256 value, bool targetReverts) public { + initialBalance = bound(initialBalance, 0, _MAX_ETHER); + value = bound(value, 0, _MAX_ETHER); - ERC2771Forwarder.ForwardRequestData memory requestData = _forgeRequestData({ + // create and sign request + ERC2771Forwarder.ForwardRequestData memory request = _forgeRequestData({ value: value, - nonce: nonce, deadline: uint48(block.timestamp + 1), data: targetReverts ? abi.encodeCall(CallReceiverMock.mockFunctionRevertsNoReason, ()) : abi.encodeCall(CallReceiverMock.mockFunction, ()) }); + _signRequestData(request, _erc2771Forwarder.nonces(_signer)); - if (targetReverts) { - vm.expectRevert(); - } + vm.deal(address(_erc2771Forwarder), initialBalance); + vm.deal(address(this), request.value); + + if (targetReverts) vm.expectRevert(); + _erc2771Forwarder.execute{value: value}(request); - _erc2771Forwarder.execute{value: value}(requestData); assertEq(address(_erc2771Forwarder).balance, initialBalance); } function testExecuteBatchAvoidsETHStuck(uint256 initialBalance, uint256 batchSize, uint256 value) public { + uint256 seed = uint256(keccak256(abi.encodePacked(initialBalance, batchSize, value))); + batchSize = bound(batchSize, 1, 10); initialBalance = bound(initialBalance, 0, _MAX_ETHER); value = bound(value, 0, _MAX_ETHER); + address refundReceiver = address(0xebe); + uint256 refundExpected = 0; + uint256 nonce = _erc2771Forwarder.nonces(_signer); + + // create an sign array or requests (that may fail) + ERC2771Forwarder.ForwardRequestData[] memory requests = new ERC2771Forwarder.ForwardRequestData[](batchSize); + for (uint256 i = 0; i < batchSize; ++i) { + bool failure = (seed >> i) & 0x1 == 0x1; + + requests[i] = _forgeRequestData({ + value: value, + deadline: uint48(block.timestamp + 1), + data: failure + ? abi.encodeCall(CallReceiverMock.mockFunctionRevertsNoReason, ()) + : abi.encodeCall(CallReceiverMock.mockFunction, ()) + }); + _signRequestData(requests[i], nonce + i); + + refundExpected += SafeCast.toUint(failure) * value; + } + + // distribute ether vm.deal(address(_erc2771Forwarder), initialBalance); + vm.deal(address(this), value * batchSize); + + // execute batch + _erc2771Forwarder.executeBatch{value: value * batchSize}(requests, payable(refundReceiver)); + + // check balances + assertEq(address(_erc2771Forwarder).balance, initialBalance); + assertEq(refundReceiver.balance, refundExpected); + } + + function testVerifyTamperedValues(uint8 _tamper) public { + TamperType tamper = _asTamper(_tamper); + + // create request, sign, tamper + ERC2771Forwarder.ForwardRequestData memory request = _forgeRequestData(); + _signRequestData(request, 0); + _tamperRequestData(request, tamper); + + // should not pass verification + assertFalse(_erc2771Forwarder.verify(request)); + } + + function testExecuteTamperedValues(uint8 _tamper) public { + TamperType tamper = _asTamper(_tamper); + + // create request, sign, tamper, expect execution revert + ERC2771Forwarder.ForwardRequestData memory request = _forgeRequestData(); + _signRequestData(request, 0); + _tamperRequestData(request, tamper); + _tamperedExpectRevert(request, tamper, 0); + + vm.deal(address(this), request.value); + _erc2771Forwarder.execute{value: request.value}(request); + } + + function testExecuteBatchTamperedValuesZeroReceiver(uint8 _tamper) public { + TamperType tamper = _asTamper(_tamper); uint256 nonce = _erc2771Forwarder.nonces(_signer); - ERC2771Forwarder.ForwardRequestData[] memory batchRequestDatas = new ERC2771Forwarder.ForwardRequestData[]( - batchSize - ); + // create an sign array or requests + ERC2771Forwarder.ForwardRequestData[] memory requests = new ERC2771Forwarder.ForwardRequestData[](3); + for (uint256 i = 0; i < requests.length; ++i) { + requests[i] = _forgeRequestData({ + value: 0, + deadline: uint48(block.timestamp + 1), + data: abi.encodeCall(CallReceiverMock.mockFunction, ()) + }); + _signRequestData(requests[i], nonce + i); + } - uint256 expectedRefund; + // tamper with request[1] and expect execution revert + _tamperRequestData(requests[1], tamper); + _tamperedExpectRevert(requests[1], tamper, nonce + 1); - for (uint256 i = 0; i < batchSize; ++i) { - bytes memory data; - bool succeed = uint256(keccak256(abi.encodePacked(initialBalance, i))) % 2 == 0; + vm.deal(address(this), requests[1].value); + _erc2771Forwarder.executeBatch{value: requests[1].value}(requests, payable(address(0))); + } - if (succeed) { - data = abi.encodeCall(CallReceiverMock.mockFunction, ()); - } else { - expectedRefund += value; - data = abi.encodeCall(CallReceiverMock.mockFunctionRevertsNoReason, ()); - } + function testExecuteBatchTamperedValues(uint8 _tamper) public { + TamperType tamper = _asTamper(_tamper); + uint256 nonce = _erc2771Forwarder.nonces(_signer); - batchRequestDatas[i] = _forgeRequestData({ - value: value, - nonce: nonce + i, + // create an sign array or requests + ERC2771Forwarder.ForwardRequestData[] memory requests = new ERC2771Forwarder.ForwardRequestData[](3); + for (uint256 i = 0; i < requests.length; ++i) { + requests[i] = _forgeRequestData({ + value: 0, deadline: uint48(block.timestamp + 1), - data: data + data: abi.encodeCall(CallReceiverMock.mockFunction, ()) }); + _signRequestData(requests[i], nonce + i); } - address payable refundReceiver = payable(address(0xebe)); - uint256 totalValue = value * batchSize; + // tamper with request[1] + _tamperRequestData(requests[1], tamper); - vm.deal(address(this), totalValue); - _erc2771Forwarder.executeBatch{value: totalValue}(batchRequestDatas, refundReceiver); + // should not revert + vm.expectCall(address(_receiver), abi.encodeCall(CallReceiverMock.mockFunction, ()), 1); - assertEq(address(_erc2771Forwarder).balance, initialBalance); - assertEq(refundReceiver.balance, expectedRefund); + vm.deal(address(this), requests[1].value); + _erc2771Forwarder.executeBatch{value: requests[1].value}(requests, payable(address(0xebe))); + } + + function _asTamper(uint8 _tamper) private pure returns (TamperType) { + return TamperType(bound(_tamper, uint8(TamperType.FROM), uint8(TamperType.SIGNATURE))); } } diff --git a/test/metatx/ERC2771Forwarder.test.js b/test/metatx/ERC2771Forwarder.test.js index 8653ad708cb..bf6cfd10c4b 100644 --- a/test/metatx/ERC2771Forwarder.test.js +++ b/test/metatx/ERC2771Forwarder.test.js @@ -52,19 +52,6 @@ async function fixture() { }; } -// values or function to tamper with a signed request. -const tamperedValues = { - from: ethers.Wallet.createRandom().address, - to: ethers.Wallet.createRandom().address, - value: ethers.parseEther('0.5'), - data: '0x1742', - signature: s => { - const t = ethers.toBeArray(s); - t[42] ^= 0xff; - return ethers.hexlify(t); - }, -}; - describe('ERC2771Forwarder', function () { beforeEach(async function () { Object.assign(this, await loadFixture(fixture)); @@ -81,15 +68,6 @@ describe('ERC2771Forwarder', function () { }); describe('with tampered values', function () { - for (const [key, value] of Object.entries(tamperedValues)) { - it(`returns false with tampered ${key}`, async function () { - const request = await this.forgeRequest(); - request[key] = typeof value == 'function' ? value(request[key]) : value; - - expect(await this.forwarder.verify(request)).to.be.false; - }); - } - it('returns false with valid signature for non-current nonce', async function () { const request = await this.forgeRequest({ nonce: 1337n }); expect(await this.forwarder.verify(request)).to.be.false; @@ -127,24 +105,6 @@ describe('ERC2771Forwarder', function () { }); describe('with tampered request', function () { - for (const [key, value] of Object.entries(tamperedValues)) { - it(`reverts with tampered ${key}`, async function () { - const request = await this.forgeRequest(); - request[key] = typeof value == 'function' ? value(request[key]) : value; - - const promise = this.forwarder.execute(request, { value: key == 'value' ? value : 0 }); - if (key != 'to') { - await expect(promise) - .to.be.revertedWithCustomError(this.forwarder, 'ERC2771ForwarderInvalidSigner') - .withArgs(ethers.verifyTypedData(this.domain, this.types, request, request.signature), request.from); - } else { - await expect(promise) - .to.be.revertedWithCustomError(this.forwarder, 'ERC2771UntrustfulTarget') - .withArgs(request.to, this.forwarder); - } - }); - } - it('reverts with valid signature for non-current nonce', async function () { const request = await this.forgeRequest(); @@ -284,26 +244,6 @@ describe('ERC2771Forwarder', function () { this.refundReceiver = ethers.ZeroAddress; }); - for (const [key, value] of Object.entries(tamperedValues)) { - it(`reverts with at least one tampered request ${key}`, async function () { - this.requests[idx][key] = typeof value == 'function' ? value(this.requests[idx][key]) : value; - - const promise = this.forwarder.executeBatch(this.requests, this.refundReceiver, { value: this.value }); - if (key != 'to') { - await expect(promise) - .to.be.revertedWithCustomError(this.forwarder, 'ERC2771ForwarderInvalidSigner') - .withArgs( - ethers.verifyTypedData(this.domain, this.types, this.requests[idx], this.requests[idx].signature), - this.requests[idx].from, - ); - } else { - await expect(promise) - .to.be.revertedWithCustomError(this.forwarder, 'ERC2771UntrustfulTarget') - .withArgs(this.requests[idx].to, this.forwarder); - } - }); - } - it('reverts with at least one valid signature for non-current nonce', async function () { // Execute first a request await this.forwarder.execute(this.requests[idx], { value: this.requests[idx].value }); @@ -340,23 +280,6 @@ describe('ERC2771Forwarder', function () { this.initialTamperedRequestNonce = await this.forwarder.nonces(this.requests[idx].from); }); - for (const [key, value] of Object.entries(tamperedValues)) { - it(`ignores a request with tampered ${key} and refunds its value`, async function () { - this.requests[idx][key] = typeof value == 'function' ? value(this.requests[idx][key]) : value; - - const events = await this.forwarder - .executeBatch(this.requests, this.refundReceiver, { value: requestsValue(this.requests) }) - .then(tx => tx.wait()) - .then(receipt => - receipt.logs.filter( - log => log?.fragment?.type == 'event' && log?.fragment?.name == 'ExecutedForwardRequest', - ), - ); - - expect(events).to.have.lengthOf(this.requests.length - 1); - }); - } - it('ignores a request with a valid signature for non-current nonce', async function () { // Execute first a request await this.forwarder.execute(this.requests[idx], { value: this.requests[idx].value }); From bc1df46eac3b897bc343952fccac3fad8a3095bc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 22 Oct 2024 09:31:20 +0200 Subject: [PATCH 38/84] Bump secp256k1 from 4.0.3 to 4.0.4 (#5275) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 37 +++++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index dc147499814..da0e2f1042d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "openzeppelin-solidity", - "version": "5.0.2", + "version": "5.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "openzeppelin-solidity", - "version": "5.0.2", + "version": "5.1.0", "license": "MIT", "devDependencies": { "@changesets/changelog-github": "^0.5.0", @@ -8935,20 +8935,41 @@ "dev": true }, "node_modules/secp256k1": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-4.0.3.tgz", - "integrity": "sha512-NLZVf+ROMxwtEj3Xa562qgv2BK5e2WNmXPiOdVIPLgs6lyTzMvBq0aWTYMI5XCP9jZMVKOcqZLw/Wc4vDkuxhA==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-4.0.4.tgz", + "integrity": "sha512-6JfvwvjUOn8F/jUoBY2Q1v5WY5XS+rj8qSe0v8Y4ezH4InLgTEeOOPQsRll9OV429Pvo6BCHGavIyJfr3TAhsw==", "dev": true, "hasInstallScript": true, "dependencies": { - "elliptic": "^6.5.4", - "node-addon-api": "^2.0.0", + "elliptic": "^6.5.7", + "node-addon-api": "^5.0.0", "node-gyp-build": "^4.2.0" }, "engines": { - "node": ">=10.0.0" + "node": ">=18.0.0" + } + }, + "node_modules/secp256k1/node_modules/elliptic": { + "version": "6.5.7", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.7.tgz", + "integrity": "sha512-ESVCtTwiA+XhY3wyh24QqRGBoP3rEdDUl3EDUUo9tft074fi19IrdpH7hLCMMP3CIj7jb3W96rn8lt/BqIlt5Q==", + "dev": true, + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" } }, + "node_modules/secp256k1/node_modules/node-addon-api": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", + "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==", + "dev": true + }, "node_modules/semver": { "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", From c343ee3768cc61a76a14d6f8402b1b19be1497fc Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Tue, 22 Oct 2024 16:57:10 +0200 Subject: [PATCH 39/84] Bump pragma to 0.8.22 for all contract that depend on ERC1967Utils (#5273) --- .changeset/seven-donkeys-tap.md | 5 ++ .github/workflows/checks.yml | 4 ++ contracts/mocks/DummyImplementation.sol | 2 +- contracts/mocks/MerkleTreeMock.sol | 2 +- contracts/mocks/Stateless.sol | 2 +- .../mocks/governance/GovernorStorageMock.sol | 2 +- contracts/mocks/proxy/UUPSUpgradeableMock.sol | 2 +- contracts/proxy/ERC1967/ERC1967Proxy.sol | 2 +- contracts/proxy/ERC1967/ERC1967Utils.sol | 2 +- contracts/proxy/beacon/BeaconProxy.sol | 2 +- contracts/proxy/transparent/ProxyAdmin.sol | 2 +- .../TransparentUpgradeableProxy.sol | 2 +- contracts/proxy/utils/UUPSUpgradeable.sol | 2 +- package.json | 3 +- scripts/checks/inheritance-ordering.js | 9 ++-- scripts/checks/pragma-consistency.js | 49 +++++++++++++++++++ 16 files changed, 76 insertions(+), 16 deletions(-) create mode 100644 .changeset/seven-donkeys-tap.md create mode 100755 scripts/checks/pragma-consistency.js diff --git a/.changeset/seven-donkeys-tap.md b/.changeset/seven-donkeys-tap.md new file mode 100644 index 00000000000..25d2305b9b8 --- /dev/null +++ b/.changeset/seven-donkeys-tap.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': patch +--- + +Update some pragma directives to ensure that all file requirements match that of the files they import. diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 9d338bb642f..a4d08c1da51 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -41,6 +41,8 @@ jobs: run: npm run test - name: Check linearisation of the inheritance graph run: npm run test:inheritance + - name: Check pragma consistency between files + run: npm run test:pragma - name: Check proceduraly generated contracts are up-to-date run: npm run test:generation - name: Compare gas costs @@ -68,6 +70,8 @@ jobs: run: npm run test - name: Check linearisation of the inheritance graph run: npm run test:inheritance + - name: Check pragma consistency between files + run: npm run test:pragma - name: Check storage layout uses: ./.github/actions/storage-layout continue-on-error: ${{ contains(github.event.pull_request.labels.*.name, 'breaking change') }} diff --git a/contracts/mocks/DummyImplementation.sol b/contracts/mocks/DummyImplementation.sol index 4925c89df4b..0f1147407f5 100644 --- a/contracts/mocks/DummyImplementation.sol +++ b/contracts/mocks/DummyImplementation.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.22; import {ERC1967Utils} from "../proxy/ERC1967/ERC1967Utils.sol"; import {StorageSlot} from "../utils/StorageSlot.sol"; diff --git a/contracts/mocks/MerkleTreeMock.sol b/contracts/mocks/MerkleTreeMock.sol index 2454efa2f91..dcde6b65884 100644 --- a/contracts/mocks/MerkleTreeMock.sol +++ b/contracts/mocks/MerkleTreeMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.20; import {MerkleTree} from "../utils/structs/MerkleTree.sol"; diff --git a/contracts/mocks/Stateless.sol b/contracts/mocks/Stateless.sol index 98e7eaf7443..9e43232d587 100644 --- a/contracts/mocks/Stateless.sol +++ b/contracts/mocks/Stateless.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.22; // We keep these imports and a dummy contract just to we can run the test suite after transpilation. diff --git a/contracts/mocks/governance/GovernorStorageMock.sol b/contracts/mocks/governance/GovernorStorageMock.sol index 88c6bf906bf..26e0e10b57a 100644 --- a/contracts/mocks/governance/GovernorStorageMock.sol +++ b/contracts/mocks/governance/GovernorStorageMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IGovernor, Governor} from "../../governance/Governor.sol"; import {GovernorTimelockControl} from "../../governance/extensions/GovernorTimelockControl.sol"; diff --git a/contracts/mocks/proxy/UUPSUpgradeableMock.sol b/contracts/mocks/proxy/UUPSUpgradeableMock.sol index a5f2d4a25d8..8c5641e6ca3 100644 --- a/contracts/mocks/proxy/UUPSUpgradeableMock.sol +++ b/contracts/mocks/proxy/UUPSUpgradeableMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.22; import {UUPSUpgradeable} from "../../proxy/utils/UUPSUpgradeable.sol"; import {ERC1967Utils} from "../../proxy/ERC1967/ERC1967Utils.sol"; diff --git a/contracts/proxy/ERC1967/ERC1967Proxy.sol b/contracts/proxy/ERC1967/ERC1967Proxy.sol index 4f51cd9578b..cad9eb5ab7e 100644 --- a/contracts/proxy/ERC1967/ERC1967Proxy.sol +++ b/contracts/proxy/ERC1967/ERC1967Proxy.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.1.0) (proxy/ERC1967/ERC1967Proxy.sol) -pragma solidity ^0.8.20; +pragma solidity ^0.8.22; import {Proxy} from "../Proxy.sol"; import {ERC1967Utils} from "./ERC1967Utils.sol"; diff --git a/contracts/proxy/ERC1967/ERC1967Utils.sol b/contracts/proxy/ERC1967/ERC1967Utils.sol index 1f320135277..287bb6beee2 100644 --- a/contracts/proxy/ERC1967/ERC1967Utils.sol +++ b/contracts/proxy/ERC1967/ERC1967Utils.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.1.0) (proxy/ERC1967/ERC1967Utils.sol) -pragma solidity ^0.8.21; +pragma solidity ^0.8.22; import {IBeacon} from "../beacon/IBeacon.sol"; import {IERC1967} from "../../interfaces/IERC1967.sol"; diff --git a/contracts/proxy/beacon/BeaconProxy.sol b/contracts/proxy/beacon/BeaconProxy.sol index 2606f21db08..e38b9d891cf 100644 --- a/contracts/proxy/beacon/BeaconProxy.sol +++ b/contracts/proxy/beacon/BeaconProxy.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.1.0) (proxy/beacon/BeaconProxy.sol) -pragma solidity ^0.8.20; +pragma solidity ^0.8.22; import {IBeacon} from "./IBeacon.sol"; import {Proxy} from "../Proxy.sol"; diff --git a/contracts/proxy/transparent/ProxyAdmin.sol b/contracts/proxy/transparent/ProxyAdmin.sol index 31772350392..2a60edfe987 100644 --- a/contracts/proxy/transparent/ProxyAdmin.sol +++ b/contracts/proxy/transparent/ProxyAdmin.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.1.0) (proxy/transparent/ProxyAdmin.sol) -pragma solidity ^0.8.20; +pragma solidity ^0.8.22; import {ITransparentUpgradeableProxy} from "./TransparentUpgradeableProxy.sol"; import {Ownable} from "../../access/Ownable.sol"; diff --git a/contracts/proxy/transparent/TransparentUpgradeableProxy.sol b/contracts/proxy/transparent/TransparentUpgradeableProxy.sol index a35a725f2b3..7342d9f8f0a 100644 --- a/contracts/proxy/transparent/TransparentUpgradeableProxy.sol +++ b/contracts/proxy/transparent/TransparentUpgradeableProxy.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.1.0) (proxy/transparent/TransparentUpgradeableProxy.sol) -pragma solidity ^0.8.20; +pragma solidity ^0.8.22; import {ERC1967Utils} from "../ERC1967/ERC1967Utils.sol"; import {ERC1967Proxy} from "../ERC1967/ERC1967Proxy.sol"; diff --git a/contracts/proxy/utils/UUPSUpgradeable.sol b/contracts/proxy/utils/UUPSUpgradeable.sol index dc799962cb3..745c56fa5d2 100644 --- a/contracts/proxy/utils/UUPSUpgradeable.sol +++ b/contracts/proxy/utils/UUPSUpgradeable.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.1.0) (proxy/utils/UUPSUpgradeable.sol) -pragma solidity ^0.8.20; +pragma solidity ^0.8.22; import {IERC1822Proxiable} from "../../interfaces/draft-IERC1822.sol"; import {ERC1967Utils} from "../ERC1967/ERC1967Utils.sol"; diff --git a/package.json b/package.json index f9b88273362..618a7efb868 100644 --- a/package.json +++ b/package.json @@ -26,8 +26,9 @@ "generate": "scripts/generate/run.js", "version": "scripts/release/version.sh", "test": "hardhat test", - "test:inheritance": "scripts/checks/inheritance-ordering.js artifacts/build-info/*", "test:generation": "scripts/checks/generation.sh", + "test:inheritance": "scripts/checks/inheritance-ordering.js artifacts/build-info/*", + "test:pragma": "scripts/checks/pragma-consistency.js artifacts/build-info/*", "gas-report": "env ENABLE_GAS_REPORT=true npm run test", "slither": "npm run clean && slither ." }, diff --git a/scripts/checks/inheritance-ordering.js b/scripts/checks/inheritance-ordering.js index 72aa37ef7b3..4ed2deec454 100755 --- a/scripts/checks/inheritance-ordering.js +++ b/scripts/checks/inheritance-ordering.js @@ -2,9 +2,13 @@ const path = require('path'); const graphlib = require('graphlib'); +const match = require('micromatch'); const { findAll } = require('solidity-ast/utils'); const { _: artifacts } = require('yargs').argv; +// files to skip +const skipPatterns = ['contracts-exposed/**', 'contracts/mocks/**']; + for (const artifact of artifacts) { const { output: solcOutput } = require(path.resolve(__dirname, '../..', artifact)); @@ -13,10 +17,7 @@ for (const artifact of artifacts) { const linearized = []; for (const source in solcOutput.contracts) { - if (['contracts-exposed/', 'contracts/mocks/'].some(pattern => source.startsWith(pattern))) { - continue; - } - + if (match.any(source, skipPatterns)) continue; for (const contractDef of findAll('ContractDefinition', solcOutput.sources[source].ast)) { names[contractDef.id] = contractDef.name; linearized.push(contractDef.linearizedBaseContracts); diff --git a/scripts/checks/pragma-consistency.js b/scripts/checks/pragma-consistency.js new file mode 100755 index 00000000000..f2f3c548f59 --- /dev/null +++ b/scripts/checks/pragma-consistency.js @@ -0,0 +1,49 @@ +#!/usr/bin/env node + +const path = require('path'); +const semver = require('semver'); +const match = require('micromatch'); +const { findAll } = require('solidity-ast/utils'); +const { _: artifacts } = require('yargs').argv; + +// files to skip +const skipPatterns = ['contracts-exposed/**', 'contracts/mocks/WithInit.sol']; + +for (const artifact of artifacts) { + const { output: solcOutput } = require(path.resolve(__dirname, '../..', artifact)); + + const pragma = {}; + + // Extract pragma directive for all files + for (const source in solcOutput.contracts) { + if (match.any(source, skipPatterns)) continue; + for (const { literals } of findAll('PragmaDirective', solcOutput.sources[source].ast)) { + // There should only be one. + const [first, ...rest] = literals; + if (first === 'solidity') pragma[source] = rest.join(''); + } + } + + // Compare the pragma directive of the file, to that of the files it imports + for (const source in solcOutput.contracts) { + if (match.any(source, skipPatterns)) continue; + // minimum version of the compiler that matches source's pragma + const minVersion = semver.minVersion(pragma[source]); + // loop over all imports in source + for (const { absolutePath } of findAll('ImportDirective', solcOutput.sources[source].ast)) { + // So files that only import without declaring anything cause issues, because they don't shop in in "pragma" + if (!pragma[absolutePath]) continue; + // Check that the minVersion for source satisfies the requirements of the imported files + if (!semver.satisfies(minVersion, pragma[absolutePath])) { + console.log( + `- ${source} uses ${pragma[source]} but depends on ${absolutePath} that requires ${pragma[absolutePath]}`, + ); + process.exitCode = 1; + } + } + } +} + +if (!process.exitCode) { + console.log('Pragma directives are consistent.'); +} From 29f40597739707296e27d01ff368e0833029fc67 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Tue, 22 Oct 2024 23:01:26 +0200 Subject: [PATCH 40/84] Bump pragma of files that depend on mcopy (#5276) --- contracts/mocks/Stateless.sol | 2 +- contracts/utils/Bytes.sol | 2 +- contracts/utils/CAIP10.sol | 2 +- contracts/utils/CAIP2.sol | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/mocks/Stateless.sol b/contracts/mocks/Stateless.sol index 9e43232d587..4ec36418466 100644 --- a/contracts/mocks/Stateless.sol +++ b/contracts/mocks/Stateless.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.22; +pragma solidity ^0.8.24; // We keep these imports and a dummy contract just to we can run the test suite after transpilation. diff --git a/contracts/utils/Bytes.sol b/contracts/utils/Bytes.sol index 84e5a3ed51f..6fe49fd8d14 100644 --- a/contracts/utils/Bytes.sol +++ b/contracts/utils/Bytes.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.24; import {Math} from "./math/Math.sol"; diff --git a/contracts/utils/CAIP10.sol b/contracts/utils/CAIP10.sol index e9ed17305b6..95aa2a97737 100644 --- a/contracts/utils/CAIP10.sol +++ b/contracts/utils/CAIP10.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.24; import {SafeCast} from "./math/SafeCast.sol"; import {Bytes} from "./Bytes.sol"; diff --git a/contracts/utils/CAIP2.sol b/contracts/utils/CAIP2.sol index 13a98f58a46..cfad67e00d0 100644 --- a/contracts/utils/CAIP2.sol +++ b/contracts/utils/CAIP2.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.24; import {SafeCast} from "./math/SafeCast.sol"; import {Bytes} from "./Bytes.sol"; From 205f59e9b6b9960d2dc61620457a4cfb05684a4a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 23 Oct 2024 08:31:39 +0200 Subject: [PATCH 41/84] Update dependency eslint to v9 (#4996) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Hadrien Croubois Co-authored-by: cairo --- .eslintrc | 20 - eslint.config.mjs | 26 ++ package-lock.json | 423 +++++++++++------- package.json | 8 +- scripts/generate/run.js | 2 +- scripts/generate/templates/Checkpoints.js | 1 - scripts/generate/templates/Checkpoints.t.js | 1 - scripts/generate/templates/EnumerableMap.js | 2 - scripts/generate/templates/EnumerableSet.js | 2 - scripts/generate/templates/MerkleProof.js | 2 - scripts/generate/templates/SafeCast.js | 2 - scripts/release/workflow/state.js | 4 +- scripts/update-docs-branch.js | 2 +- test/helpers/storage.js | 2 +- .../TransparentUpgradeableProxy.behaviour.js | 4 +- .../extensions/ERC721Consecutive.test.js | 16 +- test/utils/Address.test.js | 5 +- test/utils/Create2.test.js | 4 +- test/utils/cryptography/ECDSA.test.js | 8 +- .../SupportsInterface.behavior.js | 4 +- 20 files changed, 310 insertions(+), 228 deletions(-) delete mode 100644 .eslintrc create mode 100644 eslint.config.mjs diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index a5418c5e4e1..00000000000 --- a/.eslintrc +++ /dev/null @@ -1,20 +0,0 @@ -{ - "root": true, - "extends" : [ - "eslint:recommended", - "prettier", - ], - "env": { - "es2022": true, - "browser": true, - "node": true, - "mocha": true, - }, - "globals" : { - "artifacts": "readonly", - "contract": "readonly", - "web3": "readonly", - "extendEnvironment": "readonly", - "expect": "readonly", - } -} diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 00000000000..00fcc95bb4d --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,26 @@ +import js from '@eslint/js'; +import { includeIgnoreFile } from '@eslint/compat'; +import prettier from 'eslint-config-prettier'; +import globals from 'globals'; +import path from 'path'; + +export default [ + js.configs.recommended, + prettier, + { + languageOptions: { + ecmaVersion: 2022, + globals: { + ...globals.browser, + ...globals.mocha, + ...globals.node, + artifacts: 'readonly', + contract: 'readonly', + web3: 'readonly', + extendEnvironment: 'readonly', + expect: 'readonly', + }, + }, + }, + includeIgnoreFile(path.resolve(import.meta.dirname, '.gitignore')), +]; diff --git a/package-lock.json b/package-lock.json index da0e2f1042d..6066b617fda 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@changesets/cli": "^2.26.0", "@changesets/pre": "^2.0.0", "@changesets/read": "^0.6.0", + "@eslint/compat": "^1.2.1", "@nomicfoundation/hardhat-chai-matchers": "^2.0.6", "@nomicfoundation/hardhat-ethers": "^3.0.4", "@nomicfoundation/hardhat-network-helpers": "^1.0.3", @@ -21,10 +22,11 @@ "@openzeppelin/upgrade-safe-transpiler": "^0.3.32", "@openzeppelin/upgrades-core": "^1.20.6", "chai": "^4.2.0", - "eslint": "^8.30.0", + "eslint": "^9.0.0", "eslint-config-prettier": "^9.0.0", "ethers": "^6.7.1", "glob": "^11.0.0", + "globals": "^15.3.0", "graphlib": "^2.1.8", "hardhat": "^2.22.2", "hardhat-exposed": "^0.3.15", @@ -571,24 +573,69 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.8.1.tgz", - "integrity": "sha512-PWiOzLIUAjN/w5K17PoF4n6sKBw0gqLHPhywmYHP4t1VFQQVYeb1yWsJwnMVEMl3tUHME7X/SJPZLmtG7XBDxQ==", + "version": "4.11.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.1.tgz", + "integrity": "sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==", "dev": true, + "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, + "node_modules/@eslint/compat": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-1.2.1.tgz", + "integrity": "sha512-JbHG2TWuCeNzh87fXo+/46Z1LEo9DBA9T188d0fZgGxAD+cNyS6sx9fdiyxjGPBMyQVRlCutTByZ6a5+YMkF7g==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": "^9.10.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/@eslint/config-array": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.18.0.tgz", + "integrity": "sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.4", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.7.0.tgz", + "integrity": "sha512-xp5Jirz5DyPYlPiKat8jaq0EmYvDXKKpzTbxXMpT9eqlRJkRKIz9AGMdlvYjih+im+QlhWrpvVjl8IPC/lHlUw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@eslint/eslintrc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", - "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz", + "integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==", "dev": true, + "license": "MIT", "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", + "espree": "^10.0.1", + "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", @@ -596,7 +643,7 @@ "strip-json-comments": "^3.1.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -608,6 +655,18 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@eslint/eslintrc/node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -621,12 +680,36 @@ } }, "node_modules/@eslint/js": { - "version": "8.49.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.49.0.tgz", - "integrity": "sha512-1S8uAY/MTJqVx0SC4epBq+N2yhuwtNwLbJYNZyhL2pO1ZVKn5HFXav5T41Ryzy9K9V7ZId2JB2oy/W4aCd9/2w==", + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.13.0.tgz", + "integrity": "sha512-IFLyoY4d72Z5y/6o/BazFBezupzI/taV8sGumxTAVw3lXG9A6md1Dc34T9s1FoD/an9pJH8RHbAxsaEbBed9lA==", "dev": true, + "license": "MIT", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", + "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.1.tgz", + "integrity": "sha512-HFZ4Mp26nbWk9d/BpvP0YNL6W4UoZF0VFcTw/aPPA8RpOxeFQgK+ClABGgAUXs9Y/RGX/l1vOmrqz1MQt9MNuw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@ethereumjs/rlp": { @@ -1111,18 +1194,28 @@ "pnpm": "7.5.1" } }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.11.11", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", - "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==", + "node_modules/@humanfs/core": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.0.tgz", + "integrity": "sha512-2cbWIHbZVEweE853g8jymffCA+NCMiuqeECeBBLm8dg2oFdjuGJhgN4UAbI+6v0CKbbhvtXA4qV8YR5Ji86nmw==", "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.5", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.5.tgz", + "integrity": "sha512-KSPA4umqSG4LHYRodq31VDwKAvaTF4xmVlzM8Aeh4PlU1JQ3IG0wiA8C25d3RQ9nJyM3mBHyI53K06VVL/oFFg==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", - "minimatch": "^3.0.5" + "@humanfs/core": "^0.19.0", + "@humanwhocodes/retry": "^0.3.0" }, "engines": { - "node": ">=10.10.0" + "node": ">=18.18.0" } }, "node_modules/@humanwhocodes/module-importer": { @@ -1138,11 +1231,19 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "dev": true + "node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } }, "node_modules/@isaacs/cliui": { "version": "8.0.2", @@ -2450,6 +2551,13 @@ "@types/chai": "*" } }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", @@ -2475,6 +2583,13 @@ "ci-info": "^3.1.0" } }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/@types/lru-cache/-/lru-cache-5.1.1.tgz", @@ -2560,10 +2675,11 @@ } }, "node_modules/acorn": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", - "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.13.0.tgz", + "integrity": "sha512-8zSiw54Oxrdym50NlZ9sUusyO1Z1ZchgRLWRaK6c86XJFClyCgFKetdowBg5bKxyp/u+CDBJG4Mpp0m3HLZl9w==", "dev": true, + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -2576,6 +2692,7 @@ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, + "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } @@ -3970,18 +4087,6 @@ "node": ">=8" } }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/dotenv": { "version": "8.6.0", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", @@ -4277,57 +4382,64 @@ } }, "node_modules/eslint": { - "version": "8.49.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.49.0.tgz", - "integrity": "sha512-jw03ENfm6VJI0jA9U+8H5zfl5b+FvuU3YYvZRdZHOlU2ggJkxrlkJH4HcDrZpj6YwD8kuYqvQM8LyesoazrSOQ==", + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.13.0.tgz", + "integrity": "sha512-EYZK6SX6zjFHST/HRytOdA/zE72Cq/bfw45LSyuwrdvcclb/gqV8RRQxywOBEWO2+WDpva6UZa4CcDeJKzUCFA==", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.2", - "@eslint/js": "8.49.0", - "@humanwhocodes/config-array": "^0.11.11", + "@eslint-community/regexpp": "^4.11.0", + "@eslint/config-array": "^0.18.0", + "@eslint/core": "^0.7.0", + "@eslint/eslintrc": "^3.1.0", + "@eslint/js": "9.13.0", + "@eslint/plugin-kit": "^0.2.0", + "@humanfs/node": "^0.16.5", "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", + "@humanwhocodes/retry": "^0.3.1", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", - "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", + "eslint-scope": "^8.1.0", + "eslint-visitor-keys": "^4.1.0", + "espree": "^10.2.0", + "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", + "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", "text-table": "^0.2.0" }, "bin": { "eslint": "bin/eslint.js" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } } }, "node_modules/eslint-config-prettier": { @@ -4343,16 +4455,17 @@ } }, "node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.1.0.tgz", + "integrity": "sha512-14dSvlhaVhKKsa9Fx1l8A17s7ah7Ef7wCakJ10LYk6+GYmP9yDti2oq2SEwcyndt6knfcZyhyxwY3i9yL78EQw==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -4375,6 +4488,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, + "license": "MIT", "dependencies": { "color-convert": "^2.0.1" }, @@ -4385,17 +4499,12 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/eslint/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, "node_modules/eslint/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -4412,6 +4521,7 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, + "license": "MIT", "dependencies": { "color-name": "~1.1.4" }, @@ -4423,13 +4533,15 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/eslint/node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -4437,11 +4549,25 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.1.0.tgz", + "integrity": "sha512-Q7lok0mqMUSf5a/AdAZkA5a/gHcO6snwQClVNNvFKCAVlxXucdU8pKydU5ZVZjBx5xr37vGbFFWtLQYreLzrZg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/eslint/node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, + "license": "MIT", "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" @@ -4458,6 +4584,7 @@ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, + "license": "ISC", "dependencies": { "is-glob": "^4.0.3" }, @@ -4470,27 +4597,17 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/eslint/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, "node_modules/eslint/node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, + "license": "MIT", "dependencies": { "p-locate": "^5.0.0" }, @@ -4506,6 +4623,7 @@ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, + "license": "MIT", "dependencies": { "p-limit": "^3.0.2" }, @@ -4521,6 +4639,7 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -4529,17 +4648,31 @@ } }, "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.2.0.tgz", + "integrity": "sha512-upbkBJbckcCNBDBDXEbuhjbP68n+scUd3k/U2EkyM9nw+I/jPiL4cLF/Al06CF96wRltFda16sxDFrxsI1v0/g==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.9.0", + "acorn": "^8.12.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" + "eslint-visitor-keys": "^4.1.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.1.0.tgz", + "integrity": "sha512-Q7lok0mqMUSf5a/AdAZkA5a/gHcO6snwQClVNNvFKCAVlxXucdU8pKydU5ZVZjBx5xr37vGbFFWtLQYreLzrZg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -4575,6 +4708,7 @@ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" }, @@ -4881,15 +5015,16 @@ } }, "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, + "license": "MIT", "dependencies": { - "flat-cache": "^3.0.4" + "flat-cache": "^4.0.0" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=16.0.0" } }, "node_modules/fill-range": { @@ -4937,59 +5072,25 @@ } }, "node_modules/flat-cache": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.0.tgz", - "integrity": "sha512-OHx4Qwrrt0E4jEIcI5/Xb+f+QmJYNj2rrK8wiIdQOIrB9WrrJL8cjZvXdXuBTkkEwEqLycb5BeZDV1o2i9bTew==", - "dev": true, - "dependencies": { - "flatted": "^3.2.7", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/flat-cache/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, + "license": "MIT", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "flatted": "^3.2.9", + "keyv": "^4.5.4" }, "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/flat-cache/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=16" } }, "node_modules/flatted": { - "version": "3.2.9", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", - "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", - "dev": true + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true, + "license": "ISC" }, "node_modules/follow-redirects": { "version": "1.15.6", @@ -5326,15 +5427,13 @@ } }, "node_modules/globals": { - "version": "13.22.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.22.0.tgz", - "integrity": "sha512-H1Ddc/PbZHTDVJSnj8kWptIRSD6AM3pK+mKytuIVF4uoBV7rshFlhhvA58ceJ5wp3Er58w6zj7bykMpYXt3ETw==", + "version": "15.11.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.11.0.tgz", + "integrity": "sha512-yeyNSjdbyVaWurlwCpcA6XNBrHTMIeDdj0/hnvX/OLJ9ekOXYbLsLinH/MucQyGvNnXhidTdNhTtJaffL2sMfw==", "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -5424,12 +5523,6 @@ "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", "dev": true }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true - }, "node_modules/graphlib": { "version": "2.1.8", "resolved": "https://registry.npmjs.org/graphlib/-/graphlib-2.1.8.tgz", @@ -6643,15 +6736,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/is-plain-obj": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", @@ -6917,10 +7001,11 @@ } }, "node_modules/keyv": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.3.tgz", - "integrity": "sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug==", + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, + "license": "MIT", "dependencies": { "json-buffer": "3.0.1" } diff --git a/package.json b/package.json index 618a7efb868..19c54c92a05 100644 --- a/package.json +++ b/package.json @@ -17,8 +17,8 @@ "prepare-docs": "scripts/prepare-docs.sh", "lint": "npm run lint:js && npm run lint:sol", "lint:fix": "npm run lint:js:fix && npm run lint:sol:fix", - "lint:js": "prettier --log-level warn --ignore-path .gitignore '**/*.{js,ts}' --check && eslint --ignore-path .gitignore .", - "lint:js:fix": "prettier --log-level warn --ignore-path .gitignore '**/*.{js,ts}' --write && eslint --ignore-path .gitignore . --fix", + "lint:js": "prettier --log-level warn --ignore-path .gitignore '**/*.{js,ts}' --check && eslint .", + "lint:js:fix": "prettier --log-level warn --ignore-path .gitignore '**/*.{js,ts}' --write && eslint . --fix", "lint:sol": "prettier --log-level warn --ignore-path .gitignore '{contracts,test}/**/*.sol' --check && solhint '{contracts,test}/**/*.sol'", "lint:sol:fix": "prettier --log-level warn --ignore-path .gitignore '{contracts,test}/**/*.sol' --write", "clean": "hardhat clean && rimraf build contracts/build", @@ -55,6 +55,7 @@ "@changesets/cli": "^2.26.0", "@changesets/pre": "^2.0.0", "@changesets/read": "^0.6.0", + "@eslint/compat": "^1.2.1", "@nomicfoundation/hardhat-chai-matchers": "^2.0.6", "@nomicfoundation/hardhat-ethers": "^3.0.4", "@nomicfoundation/hardhat-network-helpers": "^1.0.3", @@ -63,9 +64,10 @@ "@openzeppelin/upgrade-safe-transpiler": "^0.3.32", "@openzeppelin/upgrades-core": "^1.20.6", "chai": "^4.2.0", - "eslint": "^8.30.0", + "eslint": "^9.0.0", "eslint-config-prettier": "^9.0.0", "ethers": "^6.7.1", + "globals": "^15.3.0", "glob": "^11.0.0", "graphlib": "^2.1.8", "hardhat": "^2.22.2", diff --git a/scripts/generate/run.js b/scripts/generate/run.js index e4947eb12bd..6779c93f44b 100755 --- a/scripts/generate/run.js +++ b/scripts/generate/run.js @@ -8,7 +8,7 @@ const format = require('./format-lines'); function getVersion(path) { try { return fs.readFileSync(path, 'utf8').match(/\/\/ OpenZeppelin Contracts \(last updated v[^)]+\)/)[0]; - } catch (err) { + } catch { return null; } } diff --git a/scripts/generate/templates/Checkpoints.js b/scripts/generate/templates/Checkpoints.js index d418b1177d1..7ec4a72532a 100644 --- a/scripts/generate/templates/Checkpoints.js +++ b/scripts/generate/templates/Checkpoints.js @@ -227,7 +227,6 @@ function _unsafeAccess( } } `; -/* eslint-enable max-len */ // GENERATE module.exports = format( diff --git a/scripts/generate/templates/Checkpoints.t.js b/scripts/generate/templates/Checkpoints.t.js index dd564e59bdd..edd2e9f98aa 100644 --- a/scripts/generate/templates/Checkpoints.t.js +++ b/scripts/generate/templates/Checkpoints.t.js @@ -11,7 +11,6 @@ import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; import {Checkpoints} from "@openzeppelin/contracts/utils/structs/Checkpoints.sol"; `; -/* eslint-disable max-len */ const template = opts => `\ using Checkpoints for Checkpoints.${opts.historyTypeName}; diff --git a/scripts/generate/templates/EnumerableMap.js b/scripts/generate/templates/EnumerableMap.js index fc896f8fb9a..c9cad6c1bc8 100644 --- a/scripts/generate/templates/EnumerableMap.js +++ b/scripts/generate/templates/EnumerableMap.js @@ -2,7 +2,6 @@ const format = require('../format-lines'); const { fromBytes32, toBytes32 } = require('./conversion'); const { TYPES } = require('./EnumerableMap.opts'); -/* eslint-disable max-len */ const header = `\ pragma solidity ^0.8.20; @@ -52,7 +51,6 @@ import {EnumerableSet} from "./EnumerableSet.sol"; * ==== */ `; -/* eslint-enable max-len */ const defaultMap = `\ // To implement this library for multiple types with as little code repetition as possible, we write it in diff --git a/scripts/generate/templates/EnumerableSet.js b/scripts/generate/templates/EnumerableSet.js index 351466b1313..02eccd0df11 100644 --- a/scripts/generate/templates/EnumerableSet.js +++ b/scripts/generate/templates/EnumerableSet.js @@ -2,7 +2,6 @@ const format = require('../format-lines'); const { fromBytes32, toBytes32 } = require('./conversion'); const { TYPES } = require('./EnumerableSet.opts'); -/* eslint-disable max-len */ const header = `\ pragma solidity ^0.8.20; @@ -41,7 +40,6 @@ pragma solidity ^0.8.20; * ==== */ `; -/* eslint-enable max-len */ const defaultSet = `\ // To implement this library for multiple types with as little code diff --git a/scripts/generate/templates/MerkleProof.js b/scripts/generate/templates/MerkleProof.js index 6711a87e28c..890b2febac4 100644 --- a/scripts/generate/templates/MerkleProof.js +++ b/scripts/generate/templates/MerkleProof.js @@ -43,7 +43,6 @@ const errors = `\ error MerkleProofInvalidMultiproof(); `; -/* eslint-disable max-len */ const templateProof = ({ suffix, location, visibility, hash }) => `\ /** * @dev Returns true if a \`leaf\` can be proved to be a part of a Merkle tree @@ -172,7 +171,6 @@ function processMultiProof${suffix}(${formatArgsMultiline( } } `; -/* eslint-enable max-len */ // GENERATE module.exports = format( diff --git a/scripts/generate/templates/SafeCast.js b/scripts/generate/templates/SafeCast.js index a3b32e3f00a..21000cf4a62 100644 --- a/scripts/generate/templates/SafeCast.js +++ b/scripts/generate/templates/SafeCast.js @@ -61,7 +61,6 @@ function toUint${length}(uint256 value) internal pure returns (uint${length}) { } `; -/* eslint-disable max-len */ const toIntDownCast = length => `\ /** * @dev Returns the downcasted int${length} from int256, reverting on @@ -81,7 +80,6 @@ function toInt${length}(int256 value) internal pure returns (int${length} downca } } `; -/* eslint-enable max-len */ const toInt = length => `\ /** diff --git a/scripts/release/workflow/state.js b/scripts/release/workflow/state.js index 914e8de0222..002f7774d47 100644 --- a/scripts/release/workflow/state.js +++ b/scripts/release/workflow/state.js @@ -106,7 +106,7 @@ async function readChangesetState(cwd = process.cwd()) { }; } -async function isPublishedOnNpm(package, version) { - const res = await fetch(`https://registry.npmjs.com/${package}/${version}`); +async function isPublishedOnNpm(packageName, version) { + const res = await fetch(`https://registry.npmjs.com/${packageName}/${version}`); return res.ok; } diff --git a/scripts/update-docs-branch.js b/scripts/update-docs-branch.js index 324ba0c67ca..cf61daad835 100644 --- a/scripts/update-docs-branch.js +++ b/scripts/update-docs-branch.js @@ -6,7 +6,7 @@ const run = cmd => { const tryRead = cmd => { try { return read(cmd); - } catch (e) { + } catch { return undefined; } }; diff --git a/test/helpers/storage.js b/test/helpers/storage.js index a75a3060d4b..466cbb10c56 100644 --- a/test/helpers/storage.js +++ b/test/helpers/storage.js @@ -26,7 +26,7 @@ const upgradeableSlot = (contractName, offset) => { // Try to get the artifact paths, will throw if it doesn't exist artifacts._getArtifactPathSync(`${contractName}Upgradeable`); return offset + ethers.toBigInt(erc7201Slot(erc7201format(contractName))); - } catch (_) { + } catch { return offset; } }; diff --git a/test/proxy/transparent/TransparentUpgradeableProxy.behaviour.js b/test/proxy/transparent/TransparentUpgradeableProxy.behaviour.js index d90bd56e2dd..8e1d62eaad3 100644 --- a/test/proxy/transparent/TransparentUpgradeableProxy.behaviour.js +++ b/test/proxy/transparent/TransparentUpgradeableProxy.behaviour.js @@ -243,10 +243,10 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy() { }); it('proxy admin cannot call delegated functions', async function () { - const interface = await ethers.getContractFactory('TransparentUpgradeableProxy'); + const factory = await ethers.getContractFactory('TransparentUpgradeableProxy'); await expect(this.instance.connect(this.proxyAdminAsSigner).delegatedFunction()).to.be.revertedWithCustomError( - interface, + factory, 'ProxyDeniedAdminAccess', ); }); diff --git a/test/token/ERC721/extensions/ERC721Consecutive.test.js b/test/token/ERC721/extensions/ERC721Consecutive.test.js index d2eda944ccd..f62d6dc5b12 100644 --- a/test/token/ERC721/extensions/ERC721Consecutive.test.js +++ b/test/token/ERC721/extensions/ERC721Consecutive.test.js @@ -202,35 +202,35 @@ describe('ERC721Consecutive', function () { const receiver = ethers.Wallet.createRandom(); it('cannot mint a batch larger than 5000', async function () { - const { interface } = await ethers.getContractFactory('$ERC721ConsecutiveMock'); + const factory = await ethers.getContractFactory('$ERC721ConsecutiveMock'); await expect(ethers.deployContract('$ERC721ConsecutiveMock', [name, symbol, 0, [], [receiver], [5001n]])) - .to.be.revertedWithCustomError({ interface }, 'ERC721ExceededMaxBatchMint') + .to.be.revertedWithCustomError(factory, 'ERC721ExceededMaxBatchMint') .withArgs(5001n, 5000n); }); it('cannot use single minting during construction', async function () { - const { interface } = await ethers.getContractFactory('$ERC721ConsecutiveNoConstructorMintMock'); + const factory = await ethers.getContractFactory('$ERC721ConsecutiveNoConstructorMintMock'); await expect( ethers.deployContract('$ERC721ConsecutiveNoConstructorMintMock', [name, symbol]), - ).to.be.revertedWithCustomError({ interface }, 'ERC721ForbiddenMint'); + ).to.be.revertedWithCustomError(factory, 'ERC721ForbiddenMint'); }); it('cannot use single minting during construction', async function () { - const { interface } = await ethers.getContractFactory('$ERC721ConsecutiveNoConstructorMintMock'); + const factory = await ethers.getContractFactory('$ERC721ConsecutiveNoConstructorMintMock'); await expect( ethers.deployContract('$ERC721ConsecutiveNoConstructorMintMock', [name, symbol]), - ).to.be.revertedWithCustomError({ interface }, 'ERC721ForbiddenMint'); + ).to.be.revertedWithCustomError(factory, 'ERC721ForbiddenMint'); }); it('consecutive mint not compatible with enumerability', async function () { - const { interface } = await ethers.getContractFactory('$ERC721ConsecutiveEnumerableMock'); + const factory = await ethers.getContractFactory('$ERC721ConsecutiveEnumerableMock'); await expect( ethers.deployContract('$ERC721ConsecutiveEnumerableMock', [name, symbol, [receiver], [100n]]), - ).to.be.revertedWithCustomError({ interface }, 'ERC721EnumerableForbiddenBatchMint'); + ).to.be.revertedWithCustomError(factory, 'ERC721EnumerableForbiddenBatchMint'); }); }); }); diff --git a/test/utils/Address.test.js b/test/utils/Address.test.js index 21775397ab2..8307a923e69 100644 --- a/test/utils/Address.test.js +++ b/test/utils/Address.test.js @@ -126,8 +126,9 @@ describe('Address', function () { }); it('reverts when function does not exist', async function () { - const interface = new ethers.Interface(['function mockFunctionDoesNotExist()']); - const call = interface.encodeFunctionData('mockFunctionDoesNotExist'); + const call = new ethers.Interface(['function mockFunctionDoesNotExist()']).encodeFunctionData( + 'mockFunctionDoesNotExist', + ); await expect(this.mock.$functionCall(this.target, call)).to.be.revertedWithCustomError(this.mock, 'FailedCall'); }); diff --git a/test/utils/Create2.test.js b/test/utils/Create2.test.js index 152fdbdf4c1..99c47a0e34a 100644 --- a/test/utils/Create2.test.js +++ b/test/utils/Create2.test.js @@ -14,13 +14,13 @@ async function fixture() { // We use a vesting wallet, with 3 constructor arguments. const constructorByteCode = await ethers .getContractFactory('VestingWallet') - .then(({ bytecode, interface }) => ethers.concat([bytecode, interface.encodeDeploy([other.address, 0n, 0n])])); + .then(factory => ethers.concat([factory.bytecode, factory.interface.encodeDeploy([other.address, 0n, 0n])])); // Bytecode for deploying a contract that has no constructor log. // Here we use the Create2 helper factory. const constructorLessBytecode = await ethers .getContractFactory('$Create2') - .then(({ bytecode, interface }) => ethers.concat([bytecode, interface.encodeDeploy([])])); + .then(factory => ethers.concat([factory.bytecode, factory.interface.encodeDeploy([])])); const mockFactory = await ethers.getContractFactory('ConstructorMock'); diff --git a/test/utils/cryptography/ECDSA.test.js b/test/utils/cryptography/ECDSA.test.js index 6b24bdbceaa..0f2879a86c4 100644 --- a/test/utils/cryptography/ECDSA.test.js +++ b/test/utils/cryptography/ECDSA.test.js @@ -26,7 +26,6 @@ describe('ECDSA', function () { it('with long signature', async function () { await expect( - // eslint-disable-next-line max-len this.mock.$recover( TEST_MESSAGE, '0x01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789', @@ -61,7 +60,6 @@ describe('ECDSA', function () { }); it('reverts with invalid signature', async function () { - // eslint-disable-next-line max-len const signature = '0x332ce75a821c982f9127538858900d87d3ec1f9f737338ad67cad133fa48feff48e6fa0c18abc62e42820f05943e47af3e9fbe306ce74d64094bdf1691ee53e01c'; await expect(this.mock.$recover(TEST_MESSAGE, signature)).to.be.revertedWithCustomError( @@ -73,7 +71,7 @@ describe('ECDSA', function () { describe('with v=27 signature', function () { const signer = '0x2cc1166f6212628A0deEf2B33BEFB2187D35b86c'; - // eslint-disable-next-line max-len + const signatureWithoutV = '0x5d99b6f7f6d1f73d1a26497f2b1c89b24c0993913f86e9a2d02cd69887d9c94f3c880358579d811b21dd1b7fd9bb01c1d81d10e69f0384e675c32b39643be892'; @@ -133,7 +131,7 @@ describe('ECDSA', function () { describe('with v=28 signature', function () { const signer = '0x1E318623aB09Fe6de3C9b8672098464Aeda9100E'; - // eslint-disable-next-line max-len + const signatureWithoutV = '0x331fe75a821c982f9127538858900d87d3ec1f9f737338ad67cad133fa48feff48e6fa0c18abc62e42820f05943e47af3e9fbe306ce74d64094bdf1691ee53e0'; @@ -193,7 +191,7 @@ describe('ECDSA', function () { it('reverts with high-s value signature', async function () { const message = '0xb94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9'; - // eslint-disable-next-line max-len + const highSSignature = '0xe742ff452d41413616a5bf43fe15dd88294e983d3d36206c2712f39083d638bde0a0fc89be718fbc1033e1d30d78be1c68081562ed2e97af876f286f3453231d1b'; diff --git a/test/utils/introspection/SupportsInterface.behavior.js b/test/utils/introspection/SupportsInterface.behavior.js index c2bd1a479b2..8a7bc4b5e2b 100644 --- a/test/utils/introspection/SupportsInterface.behavior.js +++ b/test/utils/introspection/SupportsInterface.behavior.js @@ -103,8 +103,8 @@ function shouldSupportInterfaces(interfaces = []) { describe('when the interfaceId is supported', function () { it('uses less than 30k gas', async function () { for (const k of interfaces) { - const interface = INTERFACE_IDS[k] ?? k; - expect(await this.contractUnderTest.supportsInterface.estimateGas(interface)).to.lte(30_000n); + const interfaceId = INTERFACE_IDS[k] ?? k; + expect(await this.contractUnderTest.supportsInterface.estimateGas(interfaceId)).to.lte(30_000n); } }); From 2fa4d103fe374335b15f9c54e7eec32e36ea89b4 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Wed, 23 Oct 2024 09:16:10 +0200 Subject: [PATCH 42/84] Add NoncesKeyed variant (#5272) --- .changeset/lovely-dodos-lay.md | 5 ++ contracts/mocks/Stateless.sol | 2 + contracts/utils/NoncesKeyed.sol | 60 +++++++++++++ contracts/utils/README.adoc | 3 + test/utils/Nonces.behavior.js | 152 ++++++++++++++++++++++++++++++++ test/utils/Nonces.test.js | 65 +------------- test/utils/NoncesKeyed.test.js | 17 ++++ 7 files changed, 242 insertions(+), 62 deletions(-) create mode 100644 .changeset/lovely-dodos-lay.md create mode 100644 contracts/utils/NoncesKeyed.sol create mode 100644 test/utils/Nonces.behavior.js create mode 100644 test/utils/NoncesKeyed.test.js diff --git a/.changeset/lovely-dodos-lay.md b/.changeset/lovely-dodos-lay.md new file mode 100644 index 00000000000..da225132630 --- /dev/null +++ b/.changeset/lovely-dodos-lay.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': minor +--- + +`NoncesKeyed`: Add a variant of `Nonces` that implements the ERC-4337 entrypoint nonce system. diff --git a/contracts/mocks/Stateless.sol b/contracts/mocks/Stateless.sol index 4ec36418466..3040ddcdf8e 100644 --- a/contracts/mocks/Stateless.sol +++ b/contracts/mocks/Stateless.sol @@ -29,6 +29,8 @@ import {Heap} from "../utils/structs/Heap.sol"; import {Math} from "../utils/math/Math.sol"; import {MerkleProof} from "../utils/cryptography/MerkleProof.sol"; import {MessageHashUtils} from "../utils/cryptography/MessageHashUtils.sol"; +import {Nonces} from "../utils/Nonces.sol"; +import {NoncesKeyed} from "../utils/NoncesKeyed.sol"; import {P256} from "../utils/cryptography/P256.sol"; import {Panic} from "../utils/Panic.sol"; import {Packing} from "../utils/Packing.sol"; diff --git a/contracts/utils/NoncesKeyed.sol b/contracts/utils/NoncesKeyed.sol new file mode 100644 index 00000000000..133b0c3fe4a --- /dev/null +++ b/contracts/utils/NoncesKeyed.sol @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {Nonces} from "./Nonces.sol"; + +/** + * @dev Alternative to {Nonces}, that support key-ed nonces. + * + * Follows the https://eips.ethereum.org/EIPS/eip-4337#semi-abstracted-nonce-support[ERC-4337's semi-abstracted nonce system]. + */ +abstract contract NoncesKeyed is Nonces { + mapping(address owner => mapping(uint192 key => uint64)) private _nonces; + + /// @dev Returns the next unused nonce for an address and key. Result contains the key prefix. + function nonces(address owner, uint192 key) public view virtual returns (uint256) { + return key == 0 ? nonces(owner) : ((uint256(key) << 64) | _nonces[owner][key]); + } + + /** + * @dev Consumes the next unused nonce for an address and key. + * + * Returns the current value without the key prefix. Consumed nonce is increased, so calling this functions twice + * with the same arguments will return different (sequential) results. + */ + function _useNonce(address owner, uint192 key) internal virtual returns (uint256) { + // For each account, the nonce has an initial value of 0, can only be incremented by one, and cannot be + // decremented or reset. This guarantees that the nonce never overflows. + unchecked { + // It is important to do x++ and not ++x here. + return key == 0 ? _useNonce(owner) : _nonces[owner][key]++; + } + } + + /** + * @dev Same as {_useNonce} but checking that `nonce` is the next valid for `owner`. + * + * This version takes the key and the nonce in a single uint256 parameter: + * - use the first 8 bytes for the key + * - use the last 24 bytes for the nonce + */ + function _useCheckedNonce(address owner, uint256 keyNonce) internal virtual override { + _useCheckedNonce(owner, uint192(keyNonce >> 64), uint64(keyNonce)); + } + + /** + * @dev Same as {_useNonce} but checking that `nonce` is the next valid for `owner`. + * + * This version takes the key and the nonce as two different parameters. + */ + function _useCheckedNonce(address owner, uint192 key, uint64 nonce) internal virtual { + if (key == 0) { + super._useCheckedNonce(owner, nonce); + } else { + uint256 current = _useNonce(owner, key); + if (nonce != current) { + revert InvalidAccountNonce(owner, current); + } + } + } +} diff --git a/contracts/utils/README.adoc b/contracts/utils/README.adoc index eeef84aae7c..432b806e3c4 100644 --- a/contracts/utils/README.adoc +++ b/contracts/utils/README.adoc @@ -18,6 +18,7 @@ Miscellaneous contracts and libraries containing utility functions you can use t * {ReentrancyGuardTransient}: Variant of {ReentrancyGuard} that uses transient storage (https://eips.ethereum.org/EIPS/eip-1153[EIP-1153]). * {Pausable}: A common emergency response mechanism that can pause functionality while a remediation is pending. * {Nonces}: Utility for tracking and verifying address nonces that only increment. + * {NoncesKeyed}: Alternative to {Nonces}, that support key-ed nonces following https://eips.ethereum.org/EIPS/eip-4337#semi-abstracted-nonce-support[ERC-4337 speciciations]. * {ERC165}, {ERC165Checker}: Utilities for inspecting interfaces supported by contracts. * {BitMaps}: A simple library to manage boolean value mapped to a numerical index in an efficient way. * {EnumerableMap}: A type like Solidity's https://solidity.readthedocs.io/en/latest/types.html#mapping-types[`mapping`], but with key-value _enumeration_: this will let you know how many entries a mapping has, and iterate over them (which is not possible with `mapping`). @@ -85,6 +86,8 @@ Because Solidity does not support generic types, {EnumerableMap} and {Enumerable {{Nonces}} +{{NoncesKeyed}} + == Introspection This set of interfaces and contracts deal with https://en.wikipedia.org/wiki/Type_introspection[type introspection] of contracts, that is, examining which functions can be called on them. This is usually referred to as a contract's _interface_. diff --git a/test/utils/Nonces.behavior.js b/test/utils/Nonces.behavior.js new file mode 100644 index 00000000000..17073966427 --- /dev/null +++ b/test/utils/Nonces.behavior.js @@ -0,0 +1,152 @@ +const { ethers } = require('hardhat'); +const { expect } = require('chai'); + +function shouldBehaveLikeNonces() { + describe('should behave like Nonces', function () { + const sender = ethers.Wallet.createRandom(); + const other = ethers.Wallet.createRandom(); + + it('gets a nonce', async function () { + expect(this.mock.nonces(sender)).to.eventually.equal(0n); + }); + + describe('_useNonce', function () { + it('increments a nonce', async function () { + expect(this.mock.nonces(sender)).to.eventually.equal(0n); + + const eventName = ['return$_useNonce', 'return$_useNonce_address'].find(name => + this.mock.interface.getEvent(name), + ); + + await expect(this.mock.$_useNonce(sender)).to.emit(this.mock, eventName).withArgs(0n); + + expect(this.mock.nonces(sender)).to.eventually.equal(1n); + }); + + it("increments only sender's nonce", async function () { + expect(this.mock.nonces(sender)).to.eventually.equal(0n); + expect(this.mock.nonces(other)).to.eventually.equal(0n); + + await this.mock.$_useNonce(sender); + + expect(this.mock.nonces(sender)).to.eventually.equal(1n); + expect(this.mock.nonces(other)).to.eventually.equal(0n); + }); + }); + + describe('_useCheckedNonce', function () { + it('increments a nonce', async function () { + // current nonce is 0n + expect(this.mock.nonces(sender)).to.eventually.equal(0n); + + await this.mock.$_useCheckedNonce(sender, 0n); + + expect(this.mock.nonces(sender)).to.eventually.equal(1n); + }); + + it("increments only sender's nonce", async function () { + // current nonce is 0n + expect(this.mock.nonces(sender)).to.eventually.equal(0n); + expect(this.mock.nonces(other)).to.eventually.equal(0n); + + await this.mock.$_useCheckedNonce(sender, 0n); + + expect(this.mock.nonces(sender)).to.eventually.equal(1n); + expect(this.mock.nonces(other)).to.eventually.equal(0n); + }); + + it('reverts when nonce is not the expected', async function () { + const currentNonce = await this.mock.nonces(sender); + + await expect(this.mock.$_useCheckedNonce(sender, currentNonce + 1n)) + .to.be.revertedWithCustomError(this.mock, 'InvalidAccountNonce') + .withArgs(sender, currentNonce); + }); + }); + }); +} + +function shouldBehaveLikeNoncesKeyed() { + describe('should support nonces with keys', function () { + const sender = ethers.Wallet.createRandom(); + + const keyOffset = key => key << 64n; + + it('gets a nonce', async function () { + expect(this.mock.nonces(sender, ethers.Typed.uint192(0n))).to.eventually.equal(keyOffset(0n) + 0n); + expect(this.mock.nonces(sender, ethers.Typed.uint192(17n))).to.eventually.equal(keyOffset(17n) + 0n); + }); + + describe('_useNonce', function () { + it('default variant uses key 0', async function () { + expect(this.mock.nonces(sender, ethers.Typed.uint192(0n))).to.eventually.equal(keyOffset(0n) + 0n); + expect(this.mock.nonces(sender, ethers.Typed.uint192(17n))).to.eventually.equal(keyOffset(17n) + 0n); + + await expect(this.mock.$_useNonce(sender)).to.emit(this.mock, 'return$_useNonce_address').withArgs(0n); + + await expect(this.mock.$_useNonce(sender, ethers.Typed.uint192(0n))) + .to.emit(this.mock, 'return$_useNonce_address_uint192') + .withArgs(1n); + + expect(this.mock.nonces(sender, ethers.Typed.uint192(0n))).to.eventually.equal(keyOffset(0n) + 2n); + expect(this.mock.nonces(sender, ethers.Typed.uint192(17n))).to.eventually.equal(keyOffset(17n) + 0n); + }); + + it('use nonce at another key', async function () { + expect(this.mock.nonces(sender, ethers.Typed.uint192(0n))).to.eventually.equal(keyOffset(0n) + 0n); + expect(this.mock.nonces(sender, ethers.Typed.uint192(17n))).to.eventually.equal(keyOffset(17n) + 0n); + + await expect(this.mock.$_useNonce(sender, ethers.Typed.uint192(17n))) + .to.emit(this.mock, 'return$_useNonce_address_uint192') + .withArgs(0n); + + await expect(this.mock.$_useNonce(sender, ethers.Typed.uint192(17n))) + .to.emit(this.mock, 'return$_useNonce_address_uint192') + .withArgs(1n); + + expect(this.mock.nonces(sender, ethers.Typed.uint192(0n))).to.eventually.equal(keyOffset(0n) + 0n); + expect(this.mock.nonces(sender, ethers.Typed.uint192(17n))).to.eventually.equal(keyOffset(17n) + 2n); + }); + }); + + describe('_useCheckedNonce', function () { + it('default variant uses key 0', async function () { + const currentNonce = await this.mock.nonces(sender, ethers.Typed.uint192(0n)); + + await this.mock.$_useCheckedNonce(sender, currentNonce); + + expect(this.mock.nonces(sender, ethers.Typed.uint192(0n))).to.eventually.equal(currentNonce + 1n); + }); + + it('use nonce at another key', async function () { + const currentNonce = await this.mock.nonces(sender, ethers.Typed.uint192(17n)); + + await this.mock.$_useCheckedNonce(sender, currentNonce); + + expect(this.mock.nonces(sender, ethers.Typed.uint192(17n))).to.eventually.equal(currentNonce + 1n); + }); + + it('reverts when nonce is not the expected', async function () { + const currentNonce = await this.mock.nonces(sender, ethers.Typed.uint192(42n)); + + // use and increment + await this.mock.$_useCheckedNonce(sender, currentNonce); + + // reuse same nonce + await expect(this.mock.$_useCheckedNonce(sender, currentNonce)) + .to.be.revertedWithCustomError(this.mock, 'InvalidAccountNonce') + .withArgs(sender, 1); + + // use "future" nonce too early + await expect(this.mock.$_useCheckedNonce(sender, currentNonce + 10n)) + .to.be.revertedWithCustomError(this.mock, 'InvalidAccountNonce') + .withArgs(sender, 1); + }); + }); + }); +} + +module.exports = { + shouldBehaveLikeNonces, + shouldBehaveLikeNoncesKeyed, +}; diff --git a/test/utils/Nonces.test.js b/test/utils/Nonces.test.js index 2cb4798dea6..85aa7358a00 100644 --- a/test/utils/Nonces.test.js +++ b/test/utils/Nonces.test.js @@ -1,13 +1,10 @@ const { ethers } = require('hardhat'); -const { expect } = require('chai'); const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); +const { shouldBehaveLikeNonces } = require('./Nonces.behavior'); async function fixture() { - const [sender, other] = await ethers.getSigners(); - const mock = await ethers.deployContract('$Nonces'); - - return { sender, other, mock }; + return { mock }; } describe('Nonces', function () { @@ -15,61 +12,5 @@ describe('Nonces', function () { Object.assign(this, await loadFixture(fixture)); }); - it('gets a nonce', async function () { - expect(await this.mock.nonces(this.sender)).to.equal(0n); - }); - - describe('_useNonce', function () { - it('increments a nonce', async function () { - expect(await this.mock.nonces(this.sender)).to.equal(0n); - - await expect(await this.mock.$_useNonce(this.sender)) - .to.emit(this.mock, 'return$_useNonce') - .withArgs(0n); - - expect(await this.mock.nonces(this.sender)).to.equal(1n); - }); - - it("increments only sender's nonce", async function () { - expect(await this.mock.nonces(this.sender)).to.equal(0n); - expect(await this.mock.nonces(this.other)).to.equal(0n); - - await this.mock.$_useNonce(this.sender); - - expect(await this.mock.nonces(this.sender)).to.equal(1n); - expect(await this.mock.nonces(this.other)).to.equal(0n); - }); - }); - - describe('_useCheckedNonce', function () { - it('increments a nonce', async function () { - const currentNonce = await this.mock.nonces(this.sender); - - expect(currentNonce).to.equal(0n); - - await this.mock.$_useCheckedNonce(this.sender, currentNonce); - - expect(await this.mock.nonces(this.sender)).to.equal(1n); - }); - - it("increments only sender's nonce", async function () { - const currentNonce = await this.mock.nonces(this.sender); - - expect(currentNonce).to.equal(0n); - expect(await this.mock.nonces(this.other)).to.equal(0n); - - await this.mock.$_useCheckedNonce(this.sender, currentNonce); - - expect(await this.mock.nonces(this.sender)).to.equal(1n); - expect(await this.mock.nonces(this.other)).to.equal(0n); - }); - - it('reverts when nonce is not the expected', async function () { - const currentNonce = await this.mock.nonces(this.sender); - - await expect(this.mock.$_useCheckedNonce(this.sender, currentNonce + 1n)) - .to.be.revertedWithCustomError(this.mock, 'InvalidAccountNonce') - .withArgs(this.sender, currentNonce); - }); - }); + shouldBehaveLikeNonces(); }); diff --git a/test/utils/NoncesKeyed.test.js b/test/utils/NoncesKeyed.test.js new file mode 100644 index 00000000000..c46948ee402 --- /dev/null +++ b/test/utils/NoncesKeyed.test.js @@ -0,0 +1,17 @@ +const { ethers } = require('hardhat'); +const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); +const { shouldBehaveLikeNonces, shouldBehaveLikeNoncesKeyed } = require('./Nonces.behavior'); + +async function fixture() { + const mock = await ethers.deployContract('$NoncesKeyed'); + return { mock }; +} + +describe('NoncesKeyed', function () { + beforeEach(async function () { + Object.assign(this, await loadFixture(fixture)); + }); + + shouldBehaveLikeNonces(); + shouldBehaveLikeNoncesKeyed(); +}); From 28aed34dc5e025e61ea0390c18cac875bfde1a78 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Wed, 23 Oct 2024 09:19:13 +0200 Subject: [PATCH 43/84] Merge account abstraction work into master (#5274) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernesto García Co-authored-by: Elias Rad <146735585+nnsW3@users.noreply.github.com> Co-authored-by: cairo Co-authored-by: Arr00 <13561405+arr00@users.noreply.github.com> --- .changeset/hot-shrimps-wait.md | 5 + .changeset/small-seahorses-bathe.md | 5 + .changeset/weak-roses-bathe.md | 5 + .codecov.yml | 1 + .github/workflows/checks.yml | 2 +- contracts/account/README.adoc | 12 + .../account/utils/draft-ERC4337Utils.sol | 150 +++++ .../account/utils/draft-ERC7579Utils.sol | 242 +++++++++ contracts/interfaces/draft-IERC4337.sol | 215 ++++++++ contracts/interfaces/draft-IERC7579.sol | 196 +++++++ contracts/mocks/Stateless.sol | 2 + .../mocks/account/utils/ERC7579UtilsMock.sol | 23 + contracts/utils/Packing.sol | 513 ++++++++++++++++++ scripts/generate/templates/Packing.opts.js | 2 +- scripts/generate/templates/Packing.t.js | 4 +- slither.config.json | 2 +- test/account/utils/draft-ERC4337Utils.test.js | 211 +++++++ test/account/utils/draft-ERC7579Utils.test.js | 354 ++++++++++++ test/helpers/erc4337.js | 95 ++++ test/helpers/erc7579.js | 58 ++ test/utils/Packing.t.sol | 492 ++++++++++++++--- 21 files changed, 2494 insertions(+), 95 deletions(-) create mode 100644 .changeset/hot-shrimps-wait.md create mode 100644 .changeset/small-seahorses-bathe.md create mode 100644 .changeset/weak-roses-bathe.md create mode 100644 contracts/account/README.adoc create mode 100644 contracts/account/utils/draft-ERC4337Utils.sol create mode 100644 contracts/account/utils/draft-ERC7579Utils.sol create mode 100644 contracts/interfaces/draft-IERC4337.sol create mode 100644 contracts/interfaces/draft-IERC7579.sol create mode 100644 contracts/mocks/account/utils/ERC7579UtilsMock.sol create mode 100644 test/account/utils/draft-ERC4337Utils.test.js create mode 100644 test/account/utils/draft-ERC7579Utils.test.js create mode 100644 test/helpers/erc4337.js create mode 100644 test/helpers/erc7579.js diff --git a/.changeset/hot-shrimps-wait.md b/.changeset/hot-shrimps-wait.md new file mode 100644 index 00000000000..e4e96a981ad --- /dev/null +++ b/.changeset/hot-shrimps-wait.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': minor +--- + +`Packing`: Add variants for packing `bytes10` and `bytes22` diff --git a/.changeset/small-seahorses-bathe.md b/.changeset/small-seahorses-bathe.md new file mode 100644 index 00000000000..7b5ec794f38 --- /dev/null +++ b/.changeset/small-seahorses-bathe.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': minor +--- + +`ERC7579Utils`: Add a reusable library to interact with ERC-7579 modular accounts diff --git a/.changeset/weak-roses-bathe.md b/.changeset/weak-roses-bathe.md new file mode 100644 index 00000000000..416b2e746d3 --- /dev/null +++ b/.changeset/weak-roses-bathe.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': minor +--- + +`ERC4337Utils`: Add a reusable library to manipulate user operations and interact with ERC-4337 contracts diff --git a/.codecov.yml b/.codecov.yml index 5bee9146ab9..4cec4ef7d5f 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -13,3 +13,4 @@ coverage: ignore: - "test" - "contracts/mocks" + - "contracts/vendor" diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index a4d08c1da51..18a38b3c5c0 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -133,4 +133,4 @@ jobs: with: check_hidden: true check_filenames: true - skip: package-lock.json,*.pdf + skip: package-lock.json,*.pdf,vendor diff --git a/contracts/account/README.adoc b/contracts/account/README.adoc new file mode 100644 index 00000000000..d2eb9db5ee9 --- /dev/null +++ b/contracts/account/README.adoc @@ -0,0 +1,12 @@ += Account + +[.readme-notice] +NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/api/account + +This directory includes contracts to build accounts for ERC-4337. + +== Utilities + +{{ERC4337Utils}} + +{{ERC7579Utils}} diff --git a/contracts/account/utils/draft-ERC4337Utils.sol b/contracts/account/utils/draft-ERC4337Utils.sol new file mode 100644 index 00000000000..bf559b86d6e --- /dev/null +++ b/contracts/account/utils/draft-ERC4337Utils.sol @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {IEntryPoint, PackedUserOperation} from "../../interfaces/draft-IERC4337.sol"; +import {Math} from "../../utils/math/Math.sol"; +import {Packing} from "../../utils/Packing.sol"; + +/** + * @dev Library with common ERC-4337 utility functions. + * + * See https://eips.ethereum.org/EIPS/eip-4337[ERC-4337]. + */ +library ERC4337Utils { + using Packing for *; + + /// @dev For simulation purposes, validateUserOp (and validatePaymasterUserOp) return this value on success. + uint256 internal constant SIG_VALIDATION_SUCCESS = 0; + + /// @dev For simulation purposes, validateUserOp (and validatePaymasterUserOp) must return this value in case of signature failure, instead of revert. + uint256 internal constant SIG_VALIDATION_FAILED = 1; + + /// @dev Parses the validation data into its components. See {packValidationData}. + function parseValidationData( + uint256 validationData + ) internal pure returns (address aggregator, uint48 validAfter, uint48 validUntil) { + validAfter = uint48(bytes32(validationData).extract_32_6(0x00)); + validUntil = uint48(bytes32(validationData).extract_32_6(0x06)); + aggregator = address(bytes32(validationData).extract_32_20(0x0c)); + if (validUntil == 0) validUntil = type(uint48).max; + } + + /// @dev Packs the validation data into a single uint256. See {parseValidationData}. + function packValidationData( + address aggregator, + uint48 validAfter, + uint48 validUntil + ) internal pure returns (uint256) { + return uint256(bytes6(validAfter).pack_6_6(bytes6(validUntil)).pack_12_20(bytes20(aggregator))); + } + + /// @dev Same as {packValidationData}, but with a boolean signature success flag. + function packValidationData(bool sigSuccess, uint48 validAfter, uint48 validUntil) internal pure returns (uint256) { + return + packValidationData( + address(uint160(Math.ternary(sigSuccess, SIG_VALIDATION_SUCCESS, SIG_VALIDATION_FAILED))), + validAfter, + validUntil + ); + } + + /** + * @dev Combines two validation data into a single one. + * + * The `aggregator` is set to {SIG_VALIDATION_SUCCESS} if both are successful, while + * the `validAfter` is the maximum and the `validUntil` is the minimum of both. + */ + function combineValidationData(uint256 validationData1, uint256 validationData2) internal pure returns (uint256) { + (address aggregator1, uint48 validAfter1, uint48 validUntil1) = parseValidationData(validationData1); + (address aggregator2, uint48 validAfter2, uint48 validUntil2) = parseValidationData(validationData2); + + bool success = aggregator1 == address(0) && aggregator2 == address(0); + uint48 validAfter = uint48(Math.max(validAfter1, validAfter2)); + uint48 validUntil = uint48(Math.min(validUntil1, validUntil2)); + return packValidationData(success, validAfter, validUntil); + } + + /// @dev Returns the aggregator of the `validationData` and whether it is out of time range. + function getValidationData(uint256 validationData) internal view returns (address aggregator, bool outOfTimeRange) { + (address aggregator_, uint48 validAfter, uint48 validUntil) = parseValidationData(validationData); + return (aggregator_, block.timestamp < validAfter || validUntil < block.timestamp); + } + + /// @dev Computes the hash of a user operation with the current entrypoint and chainid. + function hash(PackedUserOperation calldata self) internal view returns (bytes32) { + return hash(self, address(this), block.chainid); + } + + /// @dev Sames as {hash}, but with a custom entrypoint and chainid. + function hash( + PackedUserOperation calldata self, + address entrypoint, + uint256 chainid + ) internal pure returns (bytes32) { + bytes32 result = keccak256( + abi.encode( + keccak256( + abi.encode( + self.sender, + self.nonce, + keccak256(self.initCode), + keccak256(self.callData), + self.accountGasLimits, + self.preVerificationGas, + self.gasFees, + keccak256(self.paymasterAndData) + ) + ), + entrypoint, + chainid + ) + ); + return result; + } + + /// @dev Returns `verificationGasLimit` from the {PackedUserOperation}. + function verificationGasLimit(PackedUserOperation calldata self) internal pure returns (uint256) { + return uint128(self.accountGasLimits.extract_32_16(0x00)); + } + + /// @dev Returns `accountGasLimits` from the {PackedUserOperation}. + function callGasLimit(PackedUserOperation calldata self) internal pure returns (uint256) { + return uint128(self.accountGasLimits.extract_32_16(0x10)); + } + + /// @dev Returns the first section of `gasFees` from the {PackedUserOperation}. + function maxPriorityFeePerGas(PackedUserOperation calldata self) internal pure returns (uint256) { + return uint128(self.gasFees.extract_32_16(0x00)); + } + + /// @dev Returns the second section of `gasFees` from the {PackedUserOperation}. + function maxFeePerGas(PackedUserOperation calldata self) internal pure returns (uint256) { + return uint128(self.gasFees.extract_32_16(0x10)); + } + + /// @dev Returns the total gas price for the {PackedUserOperation} (ie. `maxFeePerGas` or `maxPriorityFeePerGas + basefee`). + function gasPrice(PackedUserOperation calldata self) internal view returns (uint256) { + unchecked { + // Following values are "per gas" + uint256 maxPriorityFee = maxPriorityFeePerGas(self); + uint256 maxFee = maxFeePerGas(self); + return Math.ternary(maxFee == maxPriorityFee, maxFee, Math.min(maxFee, maxPriorityFee + block.basefee)); + } + } + + /// @dev Returns the first section of `paymasterAndData` from the {PackedUserOperation}. + function paymaster(PackedUserOperation calldata self) internal pure returns (address) { + return address(bytes20(self.paymasterAndData[0:20])); + } + + /// @dev Returns the second section of `paymasterAndData` from the {PackedUserOperation}. + function paymasterVerificationGasLimit(PackedUserOperation calldata self) internal pure returns (uint256) { + return uint128(bytes16(self.paymasterAndData[20:36])); + } + + /// @dev Returns the third section of `paymasterAndData` from the {PackedUserOperation}. + function paymasterPostOpGasLimit(PackedUserOperation calldata self) internal pure returns (uint256) { + return uint128(bytes16(self.paymasterAndData[36:52])); + } +} diff --git a/contracts/account/utils/draft-ERC7579Utils.sol b/contracts/account/utils/draft-ERC7579Utils.sol new file mode 100644 index 00000000000..de6bb6509ec --- /dev/null +++ b/contracts/account/utils/draft-ERC7579Utils.sol @@ -0,0 +1,242 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {Execution} from "../../interfaces/draft-IERC7579.sol"; +import {Packing} from "../../utils/Packing.sol"; +import {Address} from "../../utils/Address.sol"; + +type Mode is bytes32; +type CallType is bytes1; +type ExecType is bytes1; +type ModeSelector is bytes4; +type ModePayload is bytes22; + +/** + * @dev Library with common ERC-7579 utility functions. + * + * See https://eips.ethereum.org/EIPS/eip-7579[ERC-7579]. + */ +// slither-disable-next-line unused-state +library ERC7579Utils { + using Packing for *; + + /// @dev A single `call` execution. + CallType constant CALLTYPE_SINGLE = CallType.wrap(0x00); + + /// @dev A batch of `call` executions. + CallType constant CALLTYPE_BATCH = CallType.wrap(0x01); + + /// @dev A `delegatecall` execution. + CallType constant CALLTYPE_DELEGATECALL = CallType.wrap(0xFF); + + /// @dev Default execution type that reverts on failure. + ExecType constant EXECTYPE_DEFAULT = ExecType.wrap(0x00); + + /// @dev Execution type that does not revert on failure. + ExecType constant EXECTYPE_TRY = ExecType.wrap(0x01); + + /// @dev Emits when an {EXECTYPE_TRY} execution fails. + event ERC7579TryExecuteFail(uint256 batchExecutionIndex, bytes result); + + /// @dev The provided {CallType} is not supported. + error ERC7579UnsupportedCallType(CallType callType); + + /// @dev The provided {ExecType} is not supported. + error ERC7579UnsupportedExecType(ExecType execType); + + /// @dev The provided module doesn't match the provided module type. + error ERC7579MismatchedModuleTypeId(uint256 moduleTypeId, address module); + + /// @dev The module is not installed. + error ERC7579UninstalledModule(uint256 moduleTypeId, address module); + + /// @dev The module is already installed. + error ERC7579AlreadyInstalledModule(uint256 moduleTypeId, address module); + + /// @dev The module type is not supported. + error ERC7579UnsupportedModuleType(uint256 moduleTypeId); + + /// @dev Executes a single call. + function execSingle( + ExecType execType, + bytes calldata executionCalldata + ) internal returns (bytes[] memory returnData) { + (address target, uint256 value, bytes calldata callData) = decodeSingle(executionCalldata); + returnData = new bytes[](1); + returnData[0] = _call(0, execType, target, value, callData); + } + + /// @dev Executes a batch of calls. + function execBatch( + ExecType execType, + bytes calldata executionCalldata + ) internal returns (bytes[] memory returnData) { + Execution[] calldata executionBatch = decodeBatch(executionCalldata); + returnData = new bytes[](executionBatch.length); + for (uint256 i = 0; i < executionBatch.length; ++i) { + returnData[i] = _call( + i, + execType, + executionBatch[i].target, + executionBatch[i].value, + executionBatch[i].callData + ); + } + } + + /// @dev Executes a delegate call. + function execDelegateCall( + ExecType execType, + bytes calldata executionCalldata + ) internal returns (bytes[] memory returnData) { + (address target, bytes calldata callData) = decodeDelegate(executionCalldata); + returnData = new bytes[](1); + returnData[0] = _delegatecall(0, execType, target, callData); + } + + /// @dev Encodes the mode with the provided parameters. See {decodeMode}. + function encodeMode( + CallType callType, + ExecType execType, + ModeSelector selector, + ModePayload payload + ) internal pure returns (Mode mode) { + return + Mode.wrap( + CallType + .unwrap(callType) + .pack_1_1(ExecType.unwrap(execType)) + .pack_2_4(bytes4(0)) + .pack_6_4(ModeSelector.unwrap(selector)) + .pack_10_22(ModePayload.unwrap(payload)) + ); + } + + /// @dev Decodes the mode into its parameters. See {encodeMode}. + function decodeMode( + Mode mode + ) internal pure returns (CallType callType, ExecType execType, ModeSelector selector, ModePayload payload) { + return ( + CallType.wrap(Packing.extract_32_1(Mode.unwrap(mode), 0)), + ExecType.wrap(Packing.extract_32_1(Mode.unwrap(mode), 1)), + ModeSelector.wrap(Packing.extract_32_4(Mode.unwrap(mode), 6)), + ModePayload.wrap(Packing.extract_32_22(Mode.unwrap(mode), 10)) + ); + } + + /// @dev Encodes a single call execution. See {decodeSingle}. + function encodeSingle( + address target, + uint256 value, + bytes calldata callData + ) internal pure returns (bytes memory executionCalldata) { + return abi.encodePacked(target, value, callData); + } + + /// @dev Decodes a single call execution. See {encodeSingle}. + function decodeSingle( + bytes calldata executionCalldata + ) internal pure returns (address target, uint256 value, bytes calldata callData) { + target = address(bytes20(executionCalldata[0:20])); + value = uint256(bytes32(executionCalldata[20:52])); + callData = executionCalldata[52:]; + } + + /// @dev Encodes a delegate call execution. See {decodeDelegate}. + function encodeDelegate( + address target, + bytes calldata callData + ) internal pure returns (bytes memory executionCalldata) { + return abi.encodePacked(target, callData); + } + + /// @dev Decodes a delegate call execution. See {encodeDelegate}. + function decodeDelegate( + bytes calldata executionCalldata + ) internal pure returns (address target, bytes calldata callData) { + target = address(bytes20(executionCalldata[0:20])); + callData = executionCalldata[20:]; + } + + /// @dev Encodes a batch of executions. See {decodeBatch}. + function encodeBatch(Execution[] memory executionBatch) internal pure returns (bytes memory executionCalldata) { + return abi.encode(executionBatch); + } + + /// @dev Decodes a batch of executions. See {encodeBatch}. + function decodeBatch(bytes calldata executionCalldata) internal pure returns (Execution[] calldata executionBatch) { + assembly ("memory-safe") { + let ptr := add(executionCalldata.offset, calldataload(executionCalldata.offset)) + // Extract the ERC7579 Executions + executionBatch.offset := add(ptr, 32) + executionBatch.length := calldataload(ptr) + } + } + + /// @dev Executes a `call` to the target with the provided {ExecType}. + function _call( + uint256 index, + ExecType execType, + address target, + uint256 value, + bytes calldata data + ) private returns (bytes memory) { + (bool success, bytes memory returndata) = target.call{value: value}(data); + return _validateExecutionMode(index, execType, success, returndata); + } + + /// @dev Executes a `delegatecall` to the target with the provided {ExecType}. + function _delegatecall( + uint256 index, + ExecType execType, + address target, + bytes calldata data + ) private returns (bytes memory) { + (bool success, bytes memory returndata) = target.delegatecall(data); + return _validateExecutionMode(index, execType, success, returndata); + } + + /// @dev Validates the execution mode and returns the returndata. + function _validateExecutionMode( + uint256 index, + ExecType execType, + bool success, + bytes memory returndata + ) private returns (bytes memory) { + if (execType == ERC7579Utils.EXECTYPE_DEFAULT) { + Address.verifyCallResult(success, returndata); + } else if (execType == ERC7579Utils.EXECTYPE_TRY) { + if (!success) emit ERC7579TryExecuteFail(index, returndata); + } else { + revert ERC7579UnsupportedExecType(execType); + } + return returndata; + } +} + +// Operators +using {eqCallType as ==} for CallType global; +using {eqExecType as ==} for ExecType global; +using {eqModeSelector as ==} for ModeSelector global; +using {eqModePayload as ==} for ModePayload global; + +/// @dev Compares two `CallType` values for equality. +function eqCallType(CallType a, CallType b) pure returns (bool) { + return CallType.unwrap(a) == CallType.unwrap(b); +} + +/// @dev Compares two `ExecType` values for equality. +function eqExecType(ExecType a, ExecType b) pure returns (bool) { + return ExecType.unwrap(a) == ExecType.unwrap(b); +} + +/// @dev Compares two `ModeSelector` values for equality. +function eqModeSelector(ModeSelector a, ModeSelector b) pure returns (bool) { + return ModeSelector.unwrap(a) == ModeSelector.unwrap(b); +} + +/// @dev Compares two `ModePayload` values for equality. +function eqModePayload(ModePayload a, ModePayload b) pure returns (bool) { + return ModePayload.unwrap(a) == ModePayload.unwrap(b); +} diff --git a/contracts/interfaces/draft-IERC4337.sol b/contracts/interfaces/draft-IERC4337.sol new file mode 100644 index 00000000000..9b1af56b6e5 --- /dev/null +++ b/contracts/interfaces/draft-IERC4337.sol @@ -0,0 +1,215 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +/** + * @dev A https://github.com/ethereum/ercs/blob/master/ERCS/erc-4337.md#useroperation[user operation] is composed of the following elements: + * - `sender` (`address`): The account making the operation + * - `nonce` (`uint256`): Anti-replay parameter (see “Semi-abstracted Nonce Support” ) + * - `factory` (`address`): account factory, only for new accounts + * - `factoryData` (`bytes`): data for account factory (only if account factory exists) + * - `callData` (`bytes`): The data to pass to the sender during the main execution call + * - `callGasLimit` (`uint256`): The amount of gas to allocate the main execution call + * - `verificationGasLimit` (`uint256`): The amount of gas to allocate for the verification step + * - `preVerificationGas` (`uint256`): Extra gas to pay the bunder + * - `maxFeePerGas` (`uint256`): Maximum fee per gas (similar to EIP-1559 max_fee_per_gas) + * - `maxPriorityFeePerGas` (`uint256`): Maximum priority fee per gas (similar to EIP-1559 max_priority_fee_per_gas) + * - `paymaster` (`address`): Address of paymaster contract, (or empty, if account pays for itself) + * - `paymasterVerificationGasLimit` (`uint256`): The amount of gas to allocate for the paymaster validation code + * - `paymasterPostOpGasLimit` (`uint256`): The amount of gas to allocate for the paymaster post-operation code + * - `paymasterData` (`bytes`): Data for paymaster (only if paymaster exists) + * - `signature` (`bytes`): Data passed into the account to verify authorization + * + * When passed to on-chain contacts, the following packed version is used. + * - `sender` (`address`) + * - `nonce` (`uint256`) + * - `initCode` (`bytes`): concatenation of factory address and factoryData (or empty) + * - `callData` (`bytes`) + * - `accountGasLimits` (`bytes32`): concatenation of verificationGas (16 bytes) and callGas (16 bytes) + * - `preVerificationGas` (`uint256`) + * - `gasFees` (`bytes32`): concatenation of maxPriorityFee (16 bytes) and maxFeePerGas (16 bytes) + * - `paymasterAndData` (`bytes`): concatenation of paymaster fields (or empty) + * - `signature` (`bytes`) + */ +struct PackedUserOperation { + address sender; + uint256 nonce; + bytes initCode; // `abi.encodePacked(factory, factoryData)` + bytes callData; + bytes32 accountGasLimits; // `abi.encodePacked(verificationGasLimit, callGasLimit)` 16 bytes each + uint256 preVerificationGas; + bytes32 gasFees; // `abi.encodePacked(maxPriorityFee, maxFeePerGas)` 16 bytes each + bytes paymasterAndData; // `abi.encodePacked(paymaster, paymasterVerificationGasLimit, paymasterPostOpGasLimit, paymasterData)` + bytes signature; +} + +/** + * @dev Aggregates and validates multiple signatures for a batch of user operations. + */ +interface IAggregator { + /** + * @dev Validates the signature for a user operation. + */ + function validateUserOpSignature( + PackedUserOperation calldata userOp + ) external view returns (bytes memory sigForUserOp); + + /** + * @dev Returns an aggregated signature for a batch of user operation's signatures. + */ + function aggregateSignatures( + PackedUserOperation[] calldata userOps + ) external view returns (bytes memory aggregatesSignature); + + /** + * @dev Validates that the aggregated signature is valid for the user operations. + * + * Requirements: + * + * - The aggregated signature MUST match the given list of operations. + */ + function validateSignatures(PackedUserOperation[] calldata userOps, bytes calldata signature) external view; +} + +/** + * @dev Handle nonce management for accounts. + */ +interface IEntryPointNonces { + /** + * @dev Returns the nonce for a `sender` account and a `key`. + * + * Nonces for a certain `key` are always increasing. + */ + function getNonce(address sender, uint192 key) external view returns (uint256 nonce); +} + +/** + * @dev Handle stake management for accounts. + */ +interface IEntryPointStake { + /** + * @dev Returns the balance of the account. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Deposits `msg.value` to the account. + */ + function depositTo(address account) external payable; + + /** + * @dev Withdraws `withdrawAmount` from the account to `withdrawAddress`. + */ + function withdrawTo(address payable withdrawAddress, uint256 withdrawAmount) external; + + /** + * @dev Adds stake to the account with an unstake delay of `unstakeDelaySec`. + */ + function addStake(uint32 unstakeDelaySec) external payable; + + /** + * @dev Unlocks the stake of the account. + */ + function unlockStake() external; + + /** + * @dev Withdraws the stake of the account to `withdrawAddress`. + */ + function withdrawStake(address payable withdrawAddress) external; +} + +/** + * @dev Entry point for user operations. + */ +interface IEntryPoint is IEntryPointNonces, IEntryPointStake { + /** + * @dev A user operation at `opIndex` failed with `reason`. + */ + error FailedOp(uint256 opIndex, string reason); + + /** + * @dev A user operation at `opIndex` failed with `reason` and `inner` returned data. + */ + error FailedOpWithRevert(uint256 opIndex, string reason, bytes inner); + + /** + * @dev Batch of aggregated user operations per aggregator. + */ + struct UserOpsPerAggregator { + PackedUserOperation[] userOps; + IAggregator aggregator; + bytes signature; + } + + /** + * @dev Executes a batch of user operations. + */ + function handleOps(PackedUserOperation[] calldata ops, address payable beneficiary) external; + + /** + * @dev Executes a batch of aggregated user operations per aggregator. + */ + function handleAggregatedOps( + UserOpsPerAggregator[] calldata opsPerAggregator, + address payable beneficiary + ) external; +} + +/** + * @dev Base interface for an account. + */ +interface IAccount { + /** + * @dev Validates a user operation. + */ + function validateUserOp( + PackedUserOperation calldata userOp, + bytes32 userOpHash, + uint256 missingAccountFunds + ) external returns (uint256 validationData); +} + +/** + * @dev Support for executing user operations by prepending the {executeUserOp} function selector + * to the UserOperation's `callData`. + */ +interface IAccountExecute { + /** + * @dev Executes a user operation. + */ + function executeUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash) external; +} + +/** + * @dev Interface for a paymaster contract that agrees to pay for the gas costs of a user operation. + * + * NOTE: A paymaster must hold a stake to cover the required entrypoint stake and also the gas for the transaction. + */ +interface IPaymaster { + enum PostOpMode { + opSucceeded, + opReverted, + postOpReverted + } + + /** + * @dev Validates whether the paymaster is willing to pay for the user operation. + * + * NOTE: Bundlers will reject this method if it modifies the state, unless it's whitelisted. + */ + function validatePaymasterUserOp( + PackedUserOperation calldata userOp, + bytes32 userOpHash, + uint256 maxCost + ) external returns (bytes memory context, uint256 validationData); + + /** + * @dev Verifies the sender is the entrypoint. + */ + function postOp( + PostOpMode mode, + bytes calldata context, + uint256 actualGasCost, + uint256 actualUserOpFeePerGas + ) external; +} diff --git a/contracts/interfaces/draft-IERC7579.sol b/contracts/interfaces/draft-IERC7579.sol new file mode 100644 index 00000000000..47f1627f682 --- /dev/null +++ b/contracts/interfaces/draft-IERC7579.sol @@ -0,0 +1,196 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {PackedUserOperation} from "./draft-IERC4337.sol"; + +uint256 constant VALIDATION_SUCCESS = 0; +uint256 constant VALIDATION_FAILED = 1; +uint256 constant MODULE_TYPE_VALIDATOR = 1; +uint256 constant MODULE_TYPE_EXECUTOR = 2; +uint256 constant MODULE_TYPE_FALLBACK = 3; +uint256 constant MODULE_TYPE_HOOK = 4; + +interface IERC7579Module { + /** + * @dev This function is called by the smart account during installation of the module + * @param data arbitrary data that may be required on the module during `onInstall` initialization + * + * MUST revert on error (e.g. if module is already enabled) + */ + function onInstall(bytes calldata data) external; + + /** + * @dev This function is called by the smart account during uninstallation of the module + * @param data arbitrary data that may be required on the module during `onUninstall` de-initialization + * + * MUST revert on error + */ + function onUninstall(bytes calldata data) external; + + /** + * @dev Returns boolean value if module is a certain type + * @param moduleTypeId the module type ID according the ERC-7579 spec + * + * MUST return true if the module is of the given type and false otherwise + */ + function isModuleType(uint256 moduleTypeId) external view returns (bool); +} + +interface IERC7579Validator is IERC7579Module { + /** + * @dev Validates a UserOperation + * @param userOp the ERC-4337 PackedUserOperation + * @param userOpHash the hash of the ERC-4337 PackedUserOperation + * + * MUST validate that the signature is a valid signature of the userOpHash + * SHOULD return ERC-4337's SIG_VALIDATION_FAILED (and not revert) on signature mismatch + */ + function validateUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash) external returns (uint256); + + /** + * @dev Validates a signature using ERC-1271 + * @param sender the address that sent the ERC-1271 request to the smart account + * @param hash the hash of the ERC-1271 request + * @param signature the signature of the ERC-1271 request + * + * MUST return the ERC-1271 `MAGIC_VALUE` if the signature is valid + * MUST NOT modify state + */ + function isValidSignatureWithSender( + address sender, + bytes32 hash, + bytes calldata signature + ) external view returns (bytes4); +} + +interface IERC7579Hook is IERC7579Module { + /** + * @dev Called by the smart account before execution + * @param msgSender the address that called the smart account + * @param value the value that was sent to the smart account + * @param msgData the data that was sent to the smart account + * + * MAY return arbitrary data in the `hookData` return value + */ + function preCheck( + address msgSender, + uint256 value, + bytes calldata msgData + ) external returns (bytes memory hookData); + + /** + * @dev Called by the smart account after execution + * @param hookData the data that was returned by the `preCheck` function + * + * MAY validate the `hookData` to validate transaction context of the `preCheck` function + */ + function postCheck(bytes calldata hookData) external; +} + +struct Execution { + address target; + uint256 value; + bytes callData; +} + +interface IERC7579Execution { + /** + * @dev Executes a transaction on behalf of the account. + * @param mode The encoded execution mode of the transaction. See ModeLib.sol for details + * @param executionCalldata The encoded execution call data + * + * MUST ensure adequate authorization control: e.g. onlyEntryPointOrSelf if used with ERC-4337 + * If a mode is requested that is not supported by the Account, it MUST revert + */ + function execute(bytes32 mode, bytes calldata executionCalldata) external; + + /** + * @dev Executes a transaction on behalf of the account. + * This function is intended to be called by Executor Modules + * @param mode The encoded execution mode of the transaction. See ModeLib.sol for details + * @param executionCalldata The encoded execution call data + * + * MUST ensure adequate authorization control: i.e. onlyExecutorModule + * If a mode is requested that is not supported by the Account, it MUST revert + */ + function executeFromExecutor( + bytes32 mode, + bytes calldata executionCalldata + ) external returns (bytes[] memory returnData); +} + +interface IERC7579AccountConfig { + /** + * @dev Returns the account id of the smart account + * @return accountImplementationId the account id of the smart account + * + * MUST return a non-empty string + * The accountId SHOULD be structured like so: + * "vendorname.accountname.semver" + * The id SHOULD be unique across all smart accounts + */ + function accountId() external view returns (string memory accountImplementationId); + + /** + * @dev Function to check if the account supports a certain execution mode (see above) + * @param encodedMode the encoded mode + * + * MUST return true if the account supports the mode and false otherwise + */ + function supportsExecutionMode(bytes32 encodedMode) external view returns (bool); + + /** + * @dev Function to check if the account supports a certain module typeId + * @param moduleTypeId the module type ID according to the ERC-7579 spec + * + * MUST return true if the account supports the module type and false otherwise + */ + function supportsModule(uint256 moduleTypeId) external view returns (bool); +} + +interface IERC7579ModuleConfig { + event ModuleInstalled(uint256 moduleTypeId, address module); + event ModuleUninstalled(uint256 moduleTypeId, address module); + + /** + * @dev Installs a Module of a certain type on the smart account + * @param moduleTypeId the module type ID according to the ERC-7579 spec + * @param module the module address + * @param initData arbitrary data that may be required on the module during `onInstall` + * initialization. + * + * MUST implement authorization control + * MUST call `onInstall` on the module with the `initData` parameter if provided + * MUST emit ModuleInstalled event + * MUST revert if the module is already installed or the initialization on the module failed + */ + function installModule(uint256 moduleTypeId, address module, bytes calldata initData) external; + + /** + * @dev Uninstalls a Module of a certain type on the smart account + * @param moduleTypeId the module type ID according the ERC-7579 spec + * @param module the module address + * @param deInitData arbitrary data that may be required on the module during `onInstall` + * initialization. + * + * MUST implement authorization control + * MUST call `onUninstall` on the module with the `deInitData` parameter if provided + * MUST emit ModuleUninstalled event + * MUST revert if the module is not installed or the deInitialization on the module failed + */ + function uninstallModule(uint256 moduleTypeId, address module, bytes calldata deInitData) external; + + /** + * @dev Returns whether a module is installed on the smart account + * @param moduleTypeId the module type ID according the ERC-7579 spec + * @param module the module address + * @param additionalContext arbitrary data that may be required to determine if the module is installed + * + * MUST return true if the module is installed and false otherwise + */ + function isModuleInstalled( + uint256 moduleTypeId, + address module, + bytes calldata additionalContext + ) external view returns (bool); +} diff --git a/contracts/mocks/Stateless.sol b/contracts/mocks/Stateless.sol index 3040ddcdf8e..0bc89e4ceb7 100644 --- a/contracts/mocks/Stateless.sol +++ b/contracts/mocks/Stateless.sol @@ -25,6 +25,8 @@ import {ERC165} from "../utils/introspection/ERC165.sol"; import {ERC165Checker} from "../utils/introspection/ERC165Checker.sol"; import {ERC1967Utils} from "../proxy/ERC1967/ERC1967Utils.sol"; import {ERC721Holder} from "../token/ERC721/utils/ERC721Holder.sol"; +import {ERC4337Utils} from "../account/utils/draft-ERC4337Utils.sol"; +import {ERC7579Utils} from "../account/utils/draft-ERC7579Utils.sol"; import {Heap} from "../utils/structs/Heap.sol"; import {Math} from "../utils/math/Math.sol"; import {MerkleProof} from "../utils/cryptography/MerkleProof.sol"; diff --git a/contracts/mocks/account/utils/ERC7579UtilsMock.sol b/contracts/mocks/account/utils/ERC7579UtilsMock.sol new file mode 100644 index 00000000000..e0a1e1a50ea --- /dev/null +++ b/contracts/mocks/account/utils/ERC7579UtilsMock.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {CallType, ExecType, ModeSelector, ModePayload} from "../../../account/utils/draft-ERC7579Utils.sol"; + +contract ERC7579UtilsGlobalMock { + function eqCallTypeGlobal(CallType callType1, CallType callType2) internal pure returns (bool) { + return callType1 == callType2; + } + + function eqExecTypeGlobal(ExecType execType1, ExecType execType2) internal pure returns (bool) { + return execType1 == execType2; + } + + function eqModeSelectorGlobal(ModeSelector modeSelector1, ModeSelector modeSelector2) internal pure returns (bool) { + return modeSelector1 == modeSelector2; + } + + function eqModePayloadGlobal(ModePayload modePayload1, ModePayload modePayload2) internal pure returns (bool) { + return modePayload1 == modePayload2; + } +} diff --git a/contracts/utils/Packing.sol b/contracts/utils/Packing.sol index 069153bef4a..f38e64a3bf6 100644 --- a/contracts/utils/Packing.sol +++ b/contracts/utils/Packing.sol @@ -68,6 +68,38 @@ library Packing { } } + function pack_2_8(bytes2 left, bytes8 right) internal pure returns (bytes10 result) { + assembly ("memory-safe") { + left := and(left, shl(240, not(0))) + right := and(right, shl(192, not(0))) + result := or(left, shr(16, right)) + } + } + + function pack_2_10(bytes2 left, bytes10 right) internal pure returns (bytes12 result) { + assembly ("memory-safe") { + left := and(left, shl(240, not(0))) + right := and(right, shl(176, not(0))) + result := or(left, shr(16, right)) + } + } + + function pack_2_20(bytes2 left, bytes20 right) internal pure returns (bytes22 result) { + assembly ("memory-safe") { + left := and(left, shl(240, not(0))) + right := and(right, shl(96, not(0))) + result := or(left, shr(16, right)) + } + } + + function pack_2_22(bytes2 left, bytes22 right) internal pure returns (bytes24 result) { + assembly ("memory-safe") { + left := and(left, shl(240, not(0))) + right := and(right, shl(80, not(0))) + result := or(left, shr(16, right)) + } + } + function pack_4_2(bytes4 left, bytes2 right) internal pure returns (bytes6 result) { assembly ("memory-safe") { left := and(left, shl(224, not(0))) @@ -84,6 +116,14 @@ library Packing { } } + function pack_4_6(bytes4 left, bytes6 right) internal pure returns (bytes10 result) { + assembly ("memory-safe") { + left := and(left, shl(224, not(0))) + right := and(right, shl(208, not(0))) + result := or(left, shr(32, right)) + } + } + function pack_4_8(bytes4 left, bytes8 right) internal pure returns (bytes12 result) { assembly ("memory-safe") { left := and(left, shl(224, not(0))) @@ -140,6 +180,14 @@ library Packing { } } + function pack_6_4(bytes6 left, bytes4 right) internal pure returns (bytes10 result) { + assembly ("memory-safe") { + left := and(left, shl(208, not(0))) + right := and(right, shl(224, not(0))) + result := or(left, shr(48, right)) + } + } + function pack_6_6(bytes6 left, bytes6 right) internal pure returns (bytes12 result) { assembly ("memory-safe") { left := and(left, shl(208, not(0))) @@ -148,6 +196,38 @@ library Packing { } } + function pack_6_10(bytes6 left, bytes10 right) internal pure returns (bytes16 result) { + assembly ("memory-safe") { + left := and(left, shl(208, not(0))) + right := and(right, shl(176, not(0))) + result := or(left, shr(48, right)) + } + } + + function pack_6_16(bytes6 left, bytes16 right) internal pure returns (bytes22 result) { + assembly ("memory-safe") { + left := and(left, shl(208, not(0))) + right := and(right, shl(128, not(0))) + result := or(left, shr(48, right)) + } + } + + function pack_6_22(bytes6 left, bytes22 right) internal pure returns (bytes28 result) { + assembly ("memory-safe") { + left := and(left, shl(208, not(0))) + right := and(right, shl(80, not(0))) + result := or(left, shr(48, right)) + } + } + + function pack_8_2(bytes8 left, bytes2 right) internal pure returns (bytes10 result) { + assembly ("memory-safe") { + left := and(left, shl(192, not(0))) + right := and(right, shl(240, not(0))) + result := or(left, shr(64, right)) + } + } + function pack_8_4(bytes8 left, bytes4 right) internal pure returns (bytes12 result) { assembly ("memory-safe") { left := and(left, shl(192, not(0))) @@ -196,6 +276,46 @@ library Packing { } } + function pack_10_2(bytes10 left, bytes2 right) internal pure returns (bytes12 result) { + assembly ("memory-safe") { + left := and(left, shl(176, not(0))) + right := and(right, shl(240, not(0))) + result := or(left, shr(80, right)) + } + } + + function pack_10_6(bytes10 left, bytes6 right) internal pure returns (bytes16 result) { + assembly ("memory-safe") { + left := and(left, shl(176, not(0))) + right := and(right, shl(208, not(0))) + result := or(left, shr(80, right)) + } + } + + function pack_10_10(bytes10 left, bytes10 right) internal pure returns (bytes20 result) { + assembly ("memory-safe") { + left := and(left, shl(176, not(0))) + right := and(right, shl(176, not(0))) + result := or(left, shr(80, right)) + } + } + + function pack_10_12(bytes10 left, bytes12 right) internal pure returns (bytes22 result) { + assembly ("memory-safe") { + left := and(left, shl(176, not(0))) + right := and(right, shl(160, not(0))) + result := or(left, shr(80, right)) + } + } + + function pack_10_22(bytes10 left, bytes22 right) internal pure returns (bytes32 result) { + assembly ("memory-safe") { + left := and(left, shl(176, not(0))) + right := and(right, shl(80, not(0))) + result := or(left, shr(80, right)) + } + } + function pack_12_4(bytes12 left, bytes4 right) internal pure returns (bytes16 result) { assembly ("memory-safe") { left := and(left, shl(160, not(0))) @@ -212,6 +332,14 @@ library Packing { } } + function pack_12_10(bytes12 left, bytes10 right) internal pure returns (bytes22 result) { + assembly ("memory-safe") { + left := and(left, shl(160, not(0))) + right := and(right, shl(176, not(0))) + result := or(left, shr(96, right)) + } + } + function pack_12_12(bytes12 left, bytes12 right) internal pure returns (bytes24 result) { assembly ("memory-safe") { left := and(left, shl(160, not(0))) @@ -244,6 +372,14 @@ library Packing { } } + function pack_16_6(bytes16 left, bytes6 right) internal pure returns (bytes22 result) { + assembly ("memory-safe") { + left := and(left, shl(128, not(0))) + right := and(right, shl(208, not(0))) + result := or(left, shr(128, right)) + } + } + function pack_16_8(bytes16 left, bytes8 right) internal pure returns (bytes24 result) { assembly ("memory-safe") { left := and(left, shl(128, not(0))) @@ -268,6 +404,14 @@ library Packing { } } + function pack_20_2(bytes20 left, bytes2 right) internal pure returns (bytes22 result) { + assembly ("memory-safe") { + left := and(left, shl(96, not(0))) + right := and(right, shl(240, not(0))) + result := or(left, shr(160, right)) + } + } + function pack_20_4(bytes20 left, bytes4 right) internal pure returns (bytes24 result) { assembly ("memory-safe") { left := and(left, shl(96, not(0))) @@ -292,6 +436,30 @@ library Packing { } } + function pack_22_2(bytes22 left, bytes2 right) internal pure returns (bytes24 result) { + assembly ("memory-safe") { + left := and(left, shl(80, not(0))) + right := and(right, shl(240, not(0))) + result := or(left, shr(176, right)) + } + } + + function pack_22_6(bytes22 left, bytes6 right) internal pure returns (bytes28 result) { + assembly ("memory-safe") { + left := and(left, shl(80, not(0))) + right := and(right, shl(208, not(0))) + result := or(left, shr(176, right)) + } + } + + function pack_22_10(bytes22 left, bytes10 right) internal pure returns (bytes32 result) { + assembly ("memory-safe") { + left := and(left, shl(80, not(0))) + right := and(right, shl(176, not(0))) + result := or(left, shr(176, right)) + } + } + function pack_24_4(bytes24 left, bytes4 right) internal pure returns (bytes28 result) { assembly ("memory-safe") { left := and(left, shl(64, not(0))) @@ -466,6 +634,81 @@ library Packing { } } + function extract_10_1(bytes10 self, uint8 offset) internal pure returns (bytes1 result) { + if (offset > 9) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(248, not(0))) + } + } + + function replace_10_1(bytes10 self, bytes1 value, uint8 offset) internal pure returns (bytes10 result) { + bytes1 oldValue = extract_10_1(self, offset); + assembly ("memory-safe") { + value := and(value, shl(248, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + + function extract_10_2(bytes10 self, uint8 offset) internal pure returns (bytes2 result) { + if (offset > 8) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(240, not(0))) + } + } + + function replace_10_2(bytes10 self, bytes2 value, uint8 offset) internal pure returns (bytes10 result) { + bytes2 oldValue = extract_10_2(self, offset); + assembly ("memory-safe") { + value := and(value, shl(240, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + + function extract_10_4(bytes10 self, uint8 offset) internal pure returns (bytes4 result) { + if (offset > 6) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(224, not(0))) + } + } + + function replace_10_4(bytes10 self, bytes4 value, uint8 offset) internal pure returns (bytes10 result) { + bytes4 oldValue = extract_10_4(self, offset); + assembly ("memory-safe") { + value := and(value, shl(224, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + + function extract_10_6(bytes10 self, uint8 offset) internal pure returns (bytes6 result) { + if (offset > 4) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(208, not(0))) + } + } + + function replace_10_6(bytes10 self, bytes6 value, uint8 offset) internal pure returns (bytes10 result) { + bytes6 oldValue = extract_10_6(self, offset); + assembly ("memory-safe") { + value := and(value, shl(208, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + + function extract_10_8(bytes10 self, uint8 offset) internal pure returns (bytes8 result) { + if (offset > 2) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(192, not(0))) + } + } + + function replace_10_8(bytes10 self, bytes8 value, uint8 offset) internal pure returns (bytes10 result) { + bytes8 oldValue = extract_10_8(self, offset); + assembly ("memory-safe") { + value := and(value, shl(192, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + function extract_12_1(bytes12 self, uint8 offset) internal pure returns (bytes1 result) { if (offset > 11) revert OutOfRangeAccess(); assembly ("memory-safe") { @@ -541,6 +784,21 @@ library Packing { } } + function extract_12_10(bytes12 self, uint8 offset) internal pure returns (bytes10 result) { + if (offset > 2) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(176, not(0))) + } + } + + function replace_12_10(bytes12 self, bytes10 value, uint8 offset) internal pure returns (bytes12 result) { + bytes10 oldValue = extract_12_10(self, offset); + assembly ("memory-safe") { + value := and(value, shl(176, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + function extract_16_1(bytes16 self, uint8 offset) internal pure returns (bytes1 result) { if (offset > 15) revert OutOfRangeAccess(); assembly ("memory-safe") { @@ -616,6 +874,21 @@ library Packing { } } + function extract_16_10(bytes16 self, uint8 offset) internal pure returns (bytes10 result) { + if (offset > 6) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(176, not(0))) + } + } + + function replace_16_10(bytes16 self, bytes10 value, uint8 offset) internal pure returns (bytes16 result) { + bytes10 oldValue = extract_16_10(self, offset); + assembly ("memory-safe") { + value := and(value, shl(176, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + function extract_16_12(bytes16 self, uint8 offset) internal pure returns (bytes12 result) { if (offset > 4) revert OutOfRangeAccess(); assembly ("memory-safe") { @@ -706,6 +979,21 @@ library Packing { } } + function extract_20_10(bytes20 self, uint8 offset) internal pure returns (bytes10 result) { + if (offset > 10) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(176, not(0))) + } + } + + function replace_20_10(bytes20 self, bytes10 value, uint8 offset) internal pure returns (bytes20 result) { + bytes10 oldValue = extract_20_10(self, offset); + assembly ("memory-safe") { + value := and(value, shl(176, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + function extract_20_12(bytes20 self, uint8 offset) internal pure returns (bytes12 result) { if (offset > 8) revert OutOfRangeAccess(); assembly ("memory-safe") { @@ -736,6 +1024,141 @@ library Packing { } } + function extract_22_1(bytes22 self, uint8 offset) internal pure returns (bytes1 result) { + if (offset > 21) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(248, not(0))) + } + } + + function replace_22_1(bytes22 self, bytes1 value, uint8 offset) internal pure returns (bytes22 result) { + bytes1 oldValue = extract_22_1(self, offset); + assembly ("memory-safe") { + value := and(value, shl(248, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + + function extract_22_2(bytes22 self, uint8 offset) internal pure returns (bytes2 result) { + if (offset > 20) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(240, not(0))) + } + } + + function replace_22_2(bytes22 self, bytes2 value, uint8 offset) internal pure returns (bytes22 result) { + bytes2 oldValue = extract_22_2(self, offset); + assembly ("memory-safe") { + value := and(value, shl(240, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + + function extract_22_4(bytes22 self, uint8 offset) internal pure returns (bytes4 result) { + if (offset > 18) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(224, not(0))) + } + } + + function replace_22_4(bytes22 self, bytes4 value, uint8 offset) internal pure returns (bytes22 result) { + bytes4 oldValue = extract_22_4(self, offset); + assembly ("memory-safe") { + value := and(value, shl(224, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + + function extract_22_6(bytes22 self, uint8 offset) internal pure returns (bytes6 result) { + if (offset > 16) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(208, not(0))) + } + } + + function replace_22_6(bytes22 self, bytes6 value, uint8 offset) internal pure returns (bytes22 result) { + bytes6 oldValue = extract_22_6(self, offset); + assembly ("memory-safe") { + value := and(value, shl(208, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + + function extract_22_8(bytes22 self, uint8 offset) internal pure returns (bytes8 result) { + if (offset > 14) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(192, not(0))) + } + } + + function replace_22_8(bytes22 self, bytes8 value, uint8 offset) internal pure returns (bytes22 result) { + bytes8 oldValue = extract_22_8(self, offset); + assembly ("memory-safe") { + value := and(value, shl(192, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + + function extract_22_10(bytes22 self, uint8 offset) internal pure returns (bytes10 result) { + if (offset > 12) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(176, not(0))) + } + } + + function replace_22_10(bytes22 self, bytes10 value, uint8 offset) internal pure returns (bytes22 result) { + bytes10 oldValue = extract_22_10(self, offset); + assembly ("memory-safe") { + value := and(value, shl(176, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + + function extract_22_12(bytes22 self, uint8 offset) internal pure returns (bytes12 result) { + if (offset > 10) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(160, not(0))) + } + } + + function replace_22_12(bytes22 self, bytes12 value, uint8 offset) internal pure returns (bytes22 result) { + bytes12 oldValue = extract_22_12(self, offset); + assembly ("memory-safe") { + value := and(value, shl(160, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + + function extract_22_16(bytes22 self, uint8 offset) internal pure returns (bytes16 result) { + if (offset > 6) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(128, not(0))) + } + } + + function replace_22_16(bytes22 self, bytes16 value, uint8 offset) internal pure returns (bytes22 result) { + bytes16 oldValue = extract_22_16(self, offset); + assembly ("memory-safe") { + value := and(value, shl(128, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + + function extract_22_20(bytes22 self, uint8 offset) internal pure returns (bytes20 result) { + if (offset > 2) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(96, not(0))) + } + } + + function replace_22_20(bytes22 self, bytes20 value, uint8 offset) internal pure returns (bytes22 result) { + bytes20 oldValue = extract_22_20(self, offset); + assembly ("memory-safe") { + value := and(value, shl(96, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + function extract_24_1(bytes24 self, uint8 offset) internal pure returns (bytes1 result) { if (offset > 23) revert OutOfRangeAccess(); assembly ("memory-safe") { @@ -811,6 +1234,21 @@ library Packing { } } + function extract_24_10(bytes24 self, uint8 offset) internal pure returns (bytes10 result) { + if (offset > 14) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(176, not(0))) + } + } + + function replace_24_10(bytes24 self, bytes10 value, uint8 offset) internal pure returns (bytes24 result) { + bytes10 oldValue = extract_24_10(self, offset); + assembly ("memory-safe") { + value := and(value, shl(176, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + function extract_24_12(bytes24 self, uint8 offset) internal pure returns (bytes12 result) { if (offset > 12) revert OutOfRangeAccess(); assembly ("memory-safe") { @@ -856,6 +1294,21 @@ library Packing { } } + function extract_24_22(bytes24 self, uint8 offset) internal pure returns (bytes22 result) { + if (offset > 2) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(80, not(0))) + } + } + + function replace_24_22(bytes24 self, bytes22 value, uint8 offset) internal pure returns (bytes24 result) { + bytes22 oldValue = extract_24_22(self, offset); + assembly ("memory-safe") { + value := and(value, shl(80, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + function extract_28_1(bytes28 self, uint8 offset) internal pure returns (bytes1 result) { if (offset > 27) revert OutOfRangeAccess(); assembly ("memory-safe") { @@ -931,6 +1384,21 @@ library Packing { } } + function extract_28_10(bytes28 self, uint8 offset) internal pure returns (bytes10 result) { + if (offset > 18) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(176, not(0))) + } + } + + function replace_28_10(bytes28 self, bytes10 value, uint8 offset) internal pure returns (bytes28 result) { + bytes10 oldValue = extract_28_10(self, offset); + assembly ("memory-safe") { + value := and(value, shl(176, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + function extract_28_12(bytes28 self, uint8 offset) internal pure returns (bytes12 result) { if (offset > 16) revert OutOfRangeAccess(); assembly ("memory-safe") { @@ -976,6 +1444,21 @@ library Packing { } } + function extract_28_22(bytes28 self, uint8 offset) internal pure returns (bytes22 result) { + if (offset > 6) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(80, not(0))) + } + } + + function replace_28_22(bytes28 self, bytes22 value, uint8 offset) internal pure returns (bytes28 result) { + bytes22 oldValue = extract_28_22(self, offset); + assembly ("memory-safe") { + value := and(value, shl(80, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + function extract_28_24(bytes28 self, uint8 offset) internal pure returns (bytes24 result) { if (offset > 4) revert OutOfRangeAccess(); assembly ("memory-safe") { @@ -1066,6 +1549,21 @@ library Packing { } } + function extract_32_10(bytes32 self, uint8 offset) internal pure returns (bytes10 result) { + if (offset > 22) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(176, not(0))) + } + } + + function replace_32_10(bytes32 self, bytes10 value, uint8 offset) internal pure returns (bytes32 result) { + bytes10 oldValue = extract_32_10(self, offset); + assembly ("memory-safe") { + value := and(value, shl(176, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + function extract_32_12(bytes32 self, uint8 offset) internal pure returns (bytes12 result) { if (offset > 20) revert OutOfRangeAccess(); assembly ("memory-safe") { @@ -1111,6 +1609,21 @@ library Packing { } } + function extract_32_22(bytes32 self, uint8 offset) internal pure returns (bytes22 result) { + if (offset > 10) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(80, not(0))) + } + } + + function replace_32_22(bytes32 self, bytes22 value, uint8 offset) internal pure returns (bytes32 result) { + bytes22 oldValue = extract_32_22(self, offset); + assembly ("memory-safe") { + value := and(value, shl(80, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + function extract_32_24(bytes32 self, uint8 offset) internal pure returns (bytes24 result) { if (offset > 8) revert OutOfRangeAccess(); assembly ("memory-safe") { diff --git a/scripts/generate/templates/Packing.opts.js b/scripts/generate/templates/Packing.opts.js index de9ab77ff53..893ad6297cf 100644 --- a/scripts/generate/templates/Packing.opts.js +++ b/scripts/generate/templates/Packing.opts.js @@ -1,3 +1,3 @@ module.exports = { - SIZES: [1, 2, 4, 6, 8, 12, 16, 20, 24, 28, 32], + SIZES: [1, 2, 4, 6, 8, 10, 12, 16, 20, 22, 24, 28, 32], }; diff --git a/scripts/generate/templates/Packing.t.js b/scripts/generate/templates/Packing.t.js index 56e9c0cc7c4..1feec28f5a5 100644 --- a/scripts/generate/templates/Packing.t.js +++ b/scripts/generate/templates/Packing.t.js @@ -11,14 +11,14 @@ import {Packing} from "@openzeppelin/contracts/utils/Packing.sol"; `; const testPack = (left, right) => `\ -function testPack(bytes${left} left, bytes${right} right) external { +function testPack(bytes${left} left, bytes${right} right) external pure { assertEq(left, Packing.pack_${left}_${right}(left, right).extract_${left + right}_${left}(0)); assertEq(right, Packing.pack_${left}_${right}(left, right).extract_${left + right}_${right}(${left})); } `; const testReplace = (outer, inner) => `\ -function testReplace(bytes${outer} container, bytes${inner} newValue, uint8 offset) external { +function testReplace(bytes${outer} container, bytes${inner} newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, ${outer - inner})); bytes${inner} oldValue = container.extract_${outer}_${inner}(offset); diff --git a/slither.config.json b/slither.config.json index 069da1f3a21..fa52f4dd1dd 100644 --- a/slither.config.json +++ b/slither.config.json @@ -1,5 +1,5 @@ { "detectors_to_run": "arbitrary-send-erc20,array-by-reference,incorrect-shift,name-reused,rtlo,suicidal,uninitialized-state,uninitialized-storage,arbitrary-send-erc20-permit,controlled-array-length,controlled-delegatecall,delegatecall-loop,msg-value-loop,reentrancy-eth,unchecked-transfer,weak-prng,domain-separator-collision,erc20-interface,erc721-interface,locked-ether,mapping-deletion,shadowing-abstract,tautology,write-after-write,boolean-cst,reentrancy-no-eth,reused-constructor,tx-origin,unchecked-lowlevel,unchecked-send,variable-scope,void-cst,events-access,events-maths,incorrect-unary,boolean-equal,cyclomatic-complexity,deprecated-standards,erc20-indexed,function-init-state,pragma,unused-state,reentrancy-unlimited-gas,constable-states,immutable-states,var-read-using-this", - "filter_paths": "contracts/mocks,contracts-exposed", + "filter_paths": "contracts/mocks,contracts/vendor,contracts-exposed", "compile_force_framework": "hardhat" } diff --git a/test/account/utils/draft-ERC4337Utils.test.js b/test/account/utils/draft-ERC4337Utils.test.js new file mode 100644 index 00000000000..8374bc745c9 --- /dev/null +++ b/test/account/utils/draft-ERC4337Utils.test.js @@ -0,0 +1,211 @@ +const { ethers } = require('hardhat'); +const { expect } = require('chai'); +const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); + +const { packValidationData, packPaymasterData, UserOperation } = require('../../helpers/erc4337'); +const { MAX_UINT48 } = require('../../helpers/constants'); + +const fixture = async () => { + const [authorizer, sender, entrypoint, paymaster] = await ethers.getSigners(); + const utils = await ethers.deployContract('$ERC4337Utils'); + return { utils, authorizer, sender, entrypoint, paymaster }; +}; + +describe('ERC4337Utils', function () { + beforeEach(async function () { + Object.assign(this, await loadFixture(fixture)); + }); + + describe('parseValidationData', function () { + it('parses the validation data', async function () { + const authorizer = this.authorizer; + const validUntil = 0x12345678n; + const validAfter = 0x9abcdef0n; + const validationData = packValidationData(validAfter, validUntil, authorizer); + + expect(this.utils.$parseValidationData(validationData)).to.eventually.deep.equal([ + authorizer.address, + validAfter, + validUntil, + ]); + }); + + it('returns an type(uint48).max if until is 0', async function () { + const authorizer = this.authorizer; + const validAfter = 0x12345678n; + const validationData = packValidationData(validAfter, 0, authorizer); + + expect(this.utils.$parseValidationData(validationData)).to.eventually.deep.equal([ + authorizer.address, + validAfter, + MAX_UINT48, + ]); + }); + }); + + describe('packValidationData', function () { + it('packs the validation data', async function () { + const authorizer = this.authorizer; + const validUntil = 0x12345678n; + const validAfter = 0x9abcdef0n; + const validationData = packValidationData(validAfter, validUntil, authorizer); + + expect( + this.utils.$packValidationData(ethers.Typed.address(authorizer), validAfter, validUntil), + ).to.eventually.equal(validationData); + }); + + it('packs the validation data (bool)', async function () { + const success = false; + const validUntil = 0x12345678n; + const validAfter = 0x9abcdef0n; + const validationData = packValidationData(validAfter, validUntil, false); + + expect(this.utils.$packValidationData(ethers.Typed.bool(success), validAfter, validUntil)).to.eventually.equal( + validationData, + ); + }); + }); + + describe('combineValidationData', function () { + const validUntil1 = 0x12345678n; + const validAfter1 = 0x9abcdef0n; + const validUntil2 = 0x87654321n; + const validAfter2 = 0xabcdef90n; + + it('combines the validation data', async function () { + const validationData1 = packValidationData(validAfter1, validUntil1, ethers.ZeroAddress); + const validationData2 = packValidationData(validAfter2, validUntil2, ethers.ZeroAddress); + const expected = packValidationData(validAfter2, validUntil1, true); + + // check symmetry + expect(this.utils.$combineValidationData(validationData1, validationData2)).to.eventually.equal(expected); + expect(this.utils.$combineValidationData(validationData2, validationData1)).to.eventually.equal(expected); + }); + + for (const [authorizer1, authorizer2] of [ + [ethers.ZeroAddress, '0xbf023313b891fd6000544b79e353323aa94a4f29'], + ['0xbf023313b891fd6000544b79e353323aa94a4f29', ethers.ZeroAddress], + ]) { + it('returns SIG_VALIDATION_FAILURE if one of the authorizers is not address(0)', async function () { + const validationData1 = packValidationData(validAfter1, validUntil1, authorizer1); + const validationData2 = packValidationData(validAfter2, validUntil2, authorizer2); + const expected = packValidationData(validAfter2, validUntil1, false); + + // check symmetry + expect(this.utils.$combineValidationData(validationData1, validationData2)).to.eventually.equal(expected); + expect(this.utils.$combineValidationData(validationData2, validationData1)).to.eventually.equal(expected); + }); + } + }); + + describe('getValidationData', function () { + it('returns the validation data with valid validity range', async function () { + const aggregator = this.authorizer; + const validAfter = 0; + const validUntil = MAX_UINT48; + const validationData = packValidationData(validAfter, validUntil, aggregator); + + expect(this.utils.$getValidationData(validationData)).to.eventually.deep.equal([aggregator.address, false]); + }); + + it('returns the validation data with invalid validity range (expired)', async function () { + const aggregator = this.authorizer; + const validAfter = 0; + const validUntil = 1; + const validationData = packValidationData(validAfter, validUntil, aggregator); + + expect(this.utils.$getValidationData(validationData)).to.eventually.deep.equal([aggregator.address, true]); + }); + + it('returns the validation data with invalid validity range (not yet valid)', async function () { + const aggregator = this.authorizer; + const validAfter = MAX_UINT48; + const validUntil = MAX_UINT48; + const validationData = packValidationData(validAfter, validUntil, aggregator); + + expect(this.utils.$getValidationData(validationData)).to.eventually.deep.equal([aggregator.address, true]); + }); + + it('returns address(0) and false for validationData = 0', function () { + expect(this.utils.$getValidationData(0n)).to.eventually.deep.equal([ethers.ZeroAddress, false]); + }); + }); + + describe('hash', function () { + it('returns the user operation hash', async function () { + const userOp = new UserOperation({ sender: this.sender, nonce: 1 }); + const chainId = await ethers.provider.getNetwork().then(({ chainId }) => chainId); + + expect(this.utils.$hash(userOp.packed)).to.eventually.equal(userOp.hash(this.utils.target, chainId)); + }); + + it('returns the operation hash with specified entrypoint and chainId', async function () { + const userOp = new UserOperation({ sender: this.sender, nonce: 1 }); + const chainId = 0xdeadbeef; + + expect(this.utils.$hash(userOp.packed, this.entrypoint, chainId)).to.eventually.equal( + userOp.hash(this.entrypoint, chainId), + ); + }); + }); + + describe('userOp values', function () { + it('returns verificationGasLimit', async function () { + const userOp = new UserOperation({ sender: this.sender, nonce: 1, verificationGas: 0x12345678n }); + expect(this.utils.$verificationGasLimit(userOp.packed)).to.eventually.equal(userOp.verificationGas); + }); + + it('returns callGasLimit', async function () { + const userOp = new UserOperation({ sender: this.sender, nonce: 1, callGas: 0x12345678n }); + expect(this.utils.$callGasLimit(userOp.packed)).to.eventually.equal(userOp.callGas); + }); + + it('returns maxPriorityFeePerGas', async function () { + const userOp = new UserOperation({ sender: this.sender, nonce: 1, maxPriorityFee: 0x12345678n }); + expect(this.utils.$maxPriorityFeePerGas(userOp.packed)).to.eventually.equal(userOp.maxPriorityFee); + }); + + it('returns maxFeePerGas', async function () { + const userOp = new UserOperation({ sender: this.sender, nonce: 1, maxFeePerGas: 0x12345678n }); + expect(this.utils.$maxFeePerGas(userOp.packed)).to.eventually.equal(userOp.maxFeePerGas); + }); + + it('returns gasPrice', async function () { + const userOp = new UserOperation({ + sender: this.sender, + nonce: 1, + maxPriorityFee: 0x12345678n, + maxFeePerGas: 0x87654321n, + }); + expect(this.utils.$gasPrice(userOp.packed)).to.eventually.equal(userOp.maxPriorityFee); + }); + + describe('paymasterAndData', function () { + beforeEach(async function () { + this.verificationGasLimit = 0x12345678n; + this.postOpGasLimit = 0x87654321n; + this.paymasterAndData = packPaymasterData(this.paymaster, this.verificationGasLimit, this.postOpGasLimit); + this.userOp = new UserOperation({ + sender: this.sender, + nonce: 1, + paymasterAndData: this.paymasterAndData, + }); + }); + + it('returns paymaster', async function () { + expect(this.utils.$paymaster(this.userOp.packed)).to.eventually.equal(this.paymaster); + }); + + it('returns verificationGasLimit', async function () { + expect(this.utils.$paymasterVerificationGasLimit(this.userOp.packed)).to.eventually.equal( + this.verificationGasLimit, + ); + }); + + it('returns postOpGasLimit', async function () { + expect(this.utils.$paymasterPostOpGasLimit(this.userOp.packed)).to.eventually.equal(this.postOpGasLimit); + }); + }); + }); +}); diff --git a/test/account/utils/draft-ERC7579Utils.test.js b/test/account/utils/draft-ERC7579Utils.test.js new file mode 100644 index 00000000000..cc3b70425dd --- /dev/null +++ b/test/account/utils/draft-ERC7579Utils.test.js @@ -0,0 +1,354 @@ +const { ethers } = require('hardhat'); +const { expect } = require('chai'); +const { loadFixture, setBalance } = require('@nomicfoundation/hardhat-network-helpers'); +const { + EXEC_TYPE_DEFAULT, + EXEC_TYPE_TRY, + encodeSingle, + encodeBatch, + encodeDelegate, + CALL_TYPE_CALL, + CALL_TYPE_BATCH, + encodeMode, +} = require('../../helpers/erc7579'); +const { selector } = require('../../helpers/methods'); + +const coder = ethers.AbiCoder.defaultAbiCoder(); + +const fixture = async () => { + const [sender] = await ethers.getSigners(); + const utils = await ethers.deployContract('$ERC7579Utils'); + const utilsGlobal = await ethers.deployContract('$ERC7579UtilsGlobalMock'); + const target = await ethers.deployContract('CallReceiverMock'); + const anotherTarget = await ethers.deployContract('CallReceiverMock'); + await setBalance(utils.target, ethers.parseEther('1')); + return { utils, utilsGlobal, target, anotherTarget, sender }; +}; + +describe('ERC7579Utils', function () { + beforeEach(async function () { + Object.assign(this, await loadFixture(fixture)); + }); + + describe('execSingle', function () { + it('calls the target with value', async function () { + const value = 0x012; + const data = encodeSingle(this.target, value, this.target.interface.encodeFunctionData('mockFunction')); + + await expect(this.utils.$execSingle(EXEC_TYPE_DEFAULT, data)).to.emit(this.target, 'MockFunctionCalled'); + + expect(ethers.provider.getBalance(this.target)).to.eventually.equal(value); + }); + + it('calls the target with value and args', async function () { + const value = 0x432; + const data = encodeSingle( + this.target, + value, + this.target.interface.encodeFunctionData('mockFunctionWithArgs', [42, '0x1234']), + ); + + await expect(this.utils.$execSingle(EXEC_TYPE_DEFAULT, data)) + .to.emit(this.target, 'MockFunctionCalledWithArgs') + .withArgs(42, '0x1234'); + + expect(ethers.provider.getBalance(this.target)).to.eventually.equal(value); + }); + + it('reverts when target reverts in default ExecType', async function () { + const value = 0x012; + const data = encodeSingle( + this.target, + value, + this.target.interface.encodeFunctionData('mockFunctionRevertsReason'), + ); + + await expect(this.utils.$execSingle(EXEC_TYPE_DEFAULT, data)).to.be.revertedWith('CallReceiverMock: reverting'); + }); + + it('emits ERC7579TryExecuteFail event when target reverts in try ExecType', async function () { + const value = 0x012; + const data = encodeSingle( + this.target, + value, + this.target.interface.encodeFunctionData('mockFunctionRevertsReason'), + ); + + await expect(this.utils.$execSingle(EXEC_TYPE_TRY, data)) + .to.emit(this.utils, 'ERC7579TryExecuteFail') + .withArgs( + CALL_TYPE_CALL, + ethers.solidityPacked( + ['bytes4', 'bytes'], + [selector('Error(string)'), coder.encode(['string'], ['CallReceiverMock: reverting'])], + ), + ); + }); + + it('reverts with an invalid exec type', async function () { + const value = 0x012; + const data = encodeSingle(this.target, value, this.target.interface.encodeFunctionData('mockFunction')); + + await expect(this.utils.$execSingle('0x03', data)) + .to.be.revertedWithCustomError(this.utils, 'ERC7579UnsupportedExecType') + .withArgs('0x03'); + }); + }); + + describe('execBatch', function () { + it('calls the targets with value', async function () { + const value1 = 0x012; + const value2 = 0x234; + const data = encodeBatch( + [this.target, value1, this.target.interface.encodeFunctionData('mockFunction')], + [this.anotherTarget, value2, this.anotherTarget.interface.encodeFunctionData('mockFunction')], + ); + + await expect(this.utils.$execBatch(EXEC_TYPE_DEFAULT, data)) + .to.emit(this.target, 'MockFunctionCalled') + .to.emit(this.anotherTarget, 'MockFunctionCalled'); + + expect(ethers.provider.getBalance(this.target)).to.eventually.equal(value1); + expect(ethers.provider.getBalance(this.anotherTarget)).to.eventually.equal(value2); + }); + + it('calls the targets with value and args', async function () { + const value1 = 0x012; + const value2 = 0x234; + const data = encodeBatch( + [this.target, value1, this.target.interface.encodeFunctionData('mockFunctionWithArgs', [42, '0x1234'])], + [ + this.anotherTarget, + value2, + this.anotherTarget.interface.encodeFunctionData('mockFunctionWithArgs', [42, '0x1234']), + ], + ); + + await expect(this.utils.$execBatch(EXEC_TYPE_DEFAULT, data)) + .to.emit(this.target, 'MockFunctionCalledWithArgs') + .to.emit(this.anotherTarget, 'MockFunctionCalledWithArgs'); + + expect(ethers.provider.getBalance(this.target)).to.eventually.equal(value1); + expect(ethers.provider.getBalance(this.anotherTarget)).to.eventually.equal(value2); + }); + + it('reverts when any target reverts in default ExecType', async function () { + const value1 = 0x012; + const value2 = 0x234; + const data = encodeBatch( + [this.target, value1, this.target.interface.encodeFunctionData('mockFunction')], + [this.anotherTarget, value2, this.anotherTarget.interface.encodeFunctionData('mockFunctionRevertsReason')], + ); + + await expect(this.utils.$execBatch(EXEC_TYPE_DEFAULT, data)).to.be.revertedWith('CallReceiverMock: reverting'); + }); + + it('emits ERC7579TryExecuteFail event when any target reverts in try ExecType', async function () { + const value1 = 0x012; + const value2 = 0x234; + const data = encodeBatch( + [this.target, value1, this.target.interface.encodeFunctionData('mockFunction')], + [this.anotherTarget, value2, this.anotherTarget.interface.encodeFunctionData('mockFunctionRevertsReason')], + ); + + await expect(this.utils.$execBatch(EXEC_TYPE_TRY, data)) + .to.emit(this.utils, 'ERC7579TryExecuteFail') + .withArgs( + CALL_TYPE_BATCH, + ethers.solidityPacked( + ['bytes4', 'bytes'], + [selector('Error(string)'), coder.encode(['string'], ['CallReceiverMock: reverting'])], + ), + ); + + // Check balances + expect(ethers.provider.getBalance(this.target)).to.eventually.equal(value1); + expect(ethers.provider.getBalance(this.anotherTarget)).to.eventually.equal(0); + }); + + it('reverts with an invalid exec type', async function () { + const value1 = 0x012; + const value2 = 0x234; + const data = encodeBatch( + [this.target, value1, this.target.interface.encodeFunctionData('mockFunction')], + [this.anotherTarget, value2, this.anotherTarget.interface.encodeFunctionData('mockFunction')], + ); + + await expect(this.utils.$execBatch('0x03', data)) + .to.be.revertedWithCustomError(this.utils, 'ERC7579UnsupportedExecType') + .withArgs('0x03'); + }); + }); + + describe('execDelegateCall', function () { + it('delegate calls the target', async function () { + const slot = ethers.hexlify(ethers.randomBytes(32)); + const value = ethers.hexlify(ethers.randomBytes(32)); + const data = encodeDelegate( + this.target, + this.target.interface.encodeFunctionData('mockFunctionWritesStorage', [slot, value]), + ); + + expect(ethers.provider.getStorage(this.utils.target, slot)).to.eventually.equal(ethers.ZeroHash); + await this.utils.$execDelegateCall(EXEC_TYPE_DEFAULT, data); + expect(ethers.provider.getStorage(this.utils.target, slot)).to.eventually.equal(value); + }); + + it('reverts when target reverts in default ExecType', async function () { + const data = encodeDelegate(this.target, this.target.interface.encodeFunctionData('mockFunctionRevertsReason')); + await expect(this.utils.$execDelegateCall(EXEC_TYPE_DEFAULT, data)).to.be.revertedWith( + 'CallReceiverMock: reverting', + ); + }); + + it('emits ERC7579TryExecuteFail event when target reverts in try ExecType', async function () { + const data = encodeDelegate(this.target, this.target.interface.encodeFunctionData('mockFunctionRevertsReason')); + await expect(this.utils.$execDelegateCall(EXEC_TYPE_TRY, data)) + .to.emit(this.utils, 'ERC7579TryExecuteFail') + .withArgs( + CALL_TYPE_CALL, + ethers.solidityPacked( + ['bytes4', 'bytes'], + [selector('Error(string)'), coder.encode(['string'], ['CallReceiverMock: reverting'])], + ), + ); + }); + + it('reverts with an invalid exec type', async function () { + const data = encodeDelegate(this.target, this.target.interface.encodeFunctionData('mockFunction')); + await expect(this.utils.$execDelegateCall('0x03', data)) + .to.be.revertedWithCustomError(this.utils, 'ERC7579UnsupportedExecType') + .withArgs('0x03'); + }); + }); + + it('encodes Mode', async function () { + const callType = CALL_TYPE_BATCH; + const execType = EXEC_TYPE_TRY; + const selector = '0x12345678'; + const payload = ethers.toBeHex(0, 22); + + expect(this.utils.$encodeMode(callType, execType, selector, payload)).to.eventually.equal( + encodeMode({ + callType, + execType, + selector, + payload, + }), + ); + }); + + it('decodes Mode', async function () { + const callType = CALL_TYPE_BATCH; + const execType = EXEC_TYPE_TRY; + const selector = '0x12345678'; + const payload = ethers.toBeHex(0, 22); + + expect( + this.utils.$decodeMode( + encodeMode({ + callType, + execType, + selector, + payload, + }), + ), + ).to.eventually.deep.equal([callType, execType, selector, payload]); + }); + + it('encodes single', async function () { + const target = this.target; + const value = 0x123; + const data = '0x12345678'; + + expect(this.utils.$encodeSingle(target, value, data)).to.eventually.equal(encodeSingle(target, value, data)); + }); + + it('decodes single', async function () { + const target = this.target; + const value = 0x123; + const data = '0x12345678'; + + expect(this.utils.$decodeSingle(encodeSingle(target, value, data))).to.eventually.deep.equal([ + target.target, + value, + data, + ]); + }); + + it('encodes batch', async function () { + const entries = [ + [this.target, 0x123, '0x12345678'], + [this.anotherTarget, 0x456, '0x12345678'], + ]; + + expect(this.utils.$encodeBatch(entries)).to.eventually.equal(encodeBatch(...entries)); + }); + + it('decodes batch', async function () { + const entries = [ + [this.target.target, 0x123, '0x12345678'], + [this.anotherTarget.target, 0x456, '0x12345678'], + ]; + + expect(this.utils.$decodeBatch(encodeBatch(...entries))).to.eventually.deep.equal(entries); + }); + + it('encodes delegate', async function () { + const target = this.target; + const data = '0x12345678'; + + expect(this.utils.$encodeDelegate(target, data)).to.eventually.equal(encodeDelegate(target, data)); + }); + + it('decodes delegate', async function () { + const target = this.target; + const data = '0x12345678'; + + expect(this.utils.$decodeDelegate(encodeDelegate(target, data))).to.eventually.deep.equal([target.target, data]); + }); + + describe('global', function () { + describe('eqCallTypeGlobal', function () { + it('returns true if both call types are equal', async function () { + expect(this.utilsGlobal.$eqCallTypeGlobal(CALL_TYPE_BATCH, CALL_TYPE_BATCH)).to.eventually.be.true; + }); + + it('returns false if both call types are different', async function () { + expect(this.utilsGlobal.$eqCallTypeGlobal(CALL_TYPE_CALL, CALL_TYPE_BATCH)).to.eventually.be.false; + }); + }); + + describe('eqExecTypeGlobal', function () { + it('returns true if both exec types are equal', async function () { + expect(this.utilsGlobal.$eqExecTypeGlobal(EXEC_TYPE_TRY, EXEC_TYPE_TRY)).to.eventually.be.true; + }); + + it('returns false if both exec types are different', async function () { + expect(this.utilsGlobal.$eqExecTypeGlobal(EXEC_TYPE_DEFAULT, EXEC_TYPE_TRY)).to.eventually.be.false; + }); + }); + + describe('eqModeSelectorGlobal', function () { + it('returns true if both selectors are equal', async function () { + expect(this.utilsGlobal.$eqModeSelectorGlobal('0x12345678', '0x12345678')).to.eventually.be.true; + }); + + it('returns false if both selectors are different', async function () { + expect(this.utilsGlobal.$eqModeSelectorGlobal('0x12345678', '0x87654321')).to.eventually.be.false; + }); + }); + + describe('eqModePayloadGlobal', function () { + it('returns true if both payloads are equal', async function () { + expect(this.utilsGlobal.$eqModePayloadGlobal(ethers.toBeHex(0, 22), ethers.toBeHex(0, 22))).to.eventually.be + .true; + }); + + it('returns false if both payloads are different', async function () { + expect(this.utilsGlobal.$eqModePayloadGlobal(ethers.toBeHex(0, 22), ethers.toBeHex(1, 22))).to.eventually.be + .false; + }); + }); + }); +}); diff --git a/test/helpers/erc4337.js b/test/helpers/erc4337.js new file mode 100644 index 00000000000..5901375b174 --- /dev/null +++ b/test/helpers/erc4337.js @@ -0,0 +1,95 @@ +const { ethers } = require('hardhat'); + +const SIG_VALIDATION_SUCCESS = '0x0000000000000000000000000000000000000000'; +const SIG_VALIDATION_FAILURE = '0x0000000000000000000000000000000000000001'; + +function getAddress(account) { + return account.target ?? account.address ?? account; +} + +function pack(left, right) { + return ethers.solidityPacked(['uint128', 'uint128'], [left, right]); +} + +function packValidationData(validAfter, validUntil, authorizer) { + return ethers.solidityPacked( + ['uint48', 'uint48', 'address'], + [ + validAfter, + validUntil, + typeof authorizer == 'boolean' + ? authorizer + ? SIG_VALIDATION_SUCCESS + : SIG_VALIDATION_FAILURE + : getAddress(authorizer), + ], + ); +} + +function packPaymasterData(paymaster, verificationGasLimit, postOpGasLimit) { + return ethers.solidityPacked( + ['address', 'uint128', 'uint128'], + [getAddress(paymaster), verificationGasLimit, postOpGasLimit], + ); +} + +/// Represent one user operation +class UserOperation { + constructor(params) { + this.sender = getAddress(params.sender); + this.nonce = params.nonce; + this.initCode = params.initCode ?? '0x'; + this.callData = params.callData ?? '0x'; + this.verificationGas = params.verificationGas ?? 10_000_000n; + this.callGas = params.callGas ?? 100_000n; + this.preVerificationGas = params.preVerificationGas ?? 100_000n; + this.maxPriorityFee = params.maxPriorityFee ?? 100_000n; + this.maxFeePerGas = params.maxFeePerGas ?? 100_000n; + this.paymasterAndData = params.paymasterAndData ?? '0x'; + this.signature = params.signature ?? '0x'; + } + + get packed() { + return { + sender: this.sender, + nonce: this.nonce, + initCode: this.initCode, + callData: this.callData, + accountGasLimits: pack(this.verificationGas, this.callGas), + preVerificationGas: this.preVerificationGas, + gasFees: pack(this.maxPriorityFee, this.maxFeePerGas), + paymasterAndData: this.paymasterAndData, + signature: this.signature, + }; + } + + hash(entrypoint, chainId) { + const p = this.packed; + const h = ethers.keccak256( + ethers.AbiCoder.defaultAbiCoder().encode( + ['address', 'uint256', 'bytes32', 'bytes32', 'uint256', 'uint256', 'uint256', 'uint256'], + [ + p.sender, + p.nonce, + ethers.keccak256(p.initCode), + ethers.keccak256(p.callData), + p.accountGasLimits, + p.preVerificationGas, + p.gasFees, + ethers.keccak256(p.paymasterAndData), + ], + ), + ); + return ethers.keccak256( + ethers.AbiCoder.defaultAbiCoder().encode(['bytes32', 'address', 'uint256'], [h, getAddress(entrypoint), chainId]), + ); + } +} + +module.exports = { + SIG_VALIDATION_SUCCESS, + SIG_VALIDATION_FAILURE, + packValidationData, + packPaymasterData, + UserOperation, +}; diff --git a/test/helpers/erc7579.js b/test/helpers/erc7579.js new file mode 100644 index 00000000000..6c3b4759b73 --- /dev/null +++ b/test/helpers/erc7579.js @@ -0,0 +1,58 @@ +const { ethers } = require('hardhat'); + +const MODULE_TYPE_VALIDATOR = 1; +const MODULE_TYPE_EXECUTOR = 2; +const MODULE_TYPE_FALLBACK = 3; +const MODULE_TYPE_HOOK = 4; + +const EXEC_TYPE_DEFAULT = '0x00'; +const EXEC_TYPE_TRY = '0x01'; + +const CALL_TYPE_CALL = '0x00'; +const CALL_TYPE_BATCH = '0x01'; +const CALL_TYPE_DELEGATE = '0xff'; + +const encodeMode = ({ + callType = '0x00', + execType = '0x00', + selector = '0x00000000', + payload = '0x00000000000000000000000000000000000000000000', +} = {}) => + ethers.solidityPacked( + ['bytes1', 'bytes1', 'bytes4', 'bytes4', 'bytes22'], + [callType, execType, '0x00000000', selector, payload], + ); + +const encodeSingle = (target, value = 0n, data = '0x') => + ethers.solidityPacked(['address', 'uint256', 'bytes'], [target.target ?? target.address ?? target, value, data]); + +const encodeBatch = (...entries) => + ethers.AbiCoder.defaultAbiCoder().encode( + ['(address,uint256,bytes)[]'], + [ + entries.map(entry => + Array.isArray(entry) + ? [entry[0].target ?? entry[0].address ?? entry[0], entry[1] ?? 0n, entry[2] ?? '0x'] + : [entry.target.target ?? entry.target.address ?? entry.target, entry.value ?? 0n, entry.data ?? '0x'], + ), + ], + ); + +const encodeDelegate = (target, data = '0x') => + ethers.solidityPacked(['address', 'bytes'], [target.target ?? target.address ?? target, data]); + +module.exports = { + MODULE_TYPE_VALIDATOR, + MODULE_TYPE_EXECUTOR, + MODULE_TYPE_FALLBACK, + MODULE_TYPE_HOOK, + EXEC_TYPE_DEFAULT, + EXEC_TYPE_TRY, + CALL_TYPE_CALL, + CALL_TYPE_BATCH, + CALL_TYPE_DELEGATE, + encodeMode, + encodeSingle, + encodeBatch, + encodeDelegate, +}; diff --git a/test/utils/Packing.t.sol b/test/utils/Packing.t.sol index 9531f1bffbb..40f052c80ff 100644 --- a/test/utils/Packing.t.sol +++ b/test/utils/Packing.t.sol @@ -9,182 +9,287 @@ import {Packing} from "@openzeppelin/contracts/utils/Packing.sol"; contract PackingTest is Test { using Packing for *; - function testPack(bytes1 left, bytes1 right) external { + function testPack(bytes1 left, bytes1 right) external pure { assertEq(left, Packing.pack_1_1(left, right).extract_2_1(0)); assertEq(right, Packing.pack_1_1(left, right).extract_2_1(1)); } - function testPack(bytes2 left, bytes2 right) external { + function testPack(bytes2 left, bytes2 right) external pure { assertEq(left, Packing.pack_2_2(left, right).extract_4_2(0)); assertEq(right, Packing.pack_2_2(left, right).extract_4_2(2)); } - function testPack(bytes2 left, bytes4 right) external { + function testPack(bytes2 left, bytes4 right) external pure { assertEq(left, Packing.pack_2_4(left, right).extract_6_2(0)); assertEq(right, Packing.pack_2_4(left, right).extract_6_4(2)); } - function testPack(bytes2 left, bytes6 right) external { + function testPack(bytes2 left, bytes6 right) external pure { assertEq(left, Packing.pack_2_6(left, right).extract_8_2(0)); assertEq(right, Packing.pack_2_6(left, right).extract_8_6(2)); } - function testPack(bytes4 left, bytes2 right) external { + function testPack(bytes2 left, bytes8 right) external pure { + assertEq(left, Packing.pack_2_8(left, right).extract_10_2(0)); + assertEq(right, Packing.pack_2_8(left, right).extract_10_8(2)); + } + + function testPack(bytes2 left, bytes10 right) external pure { + assertEq(left, Packing.pack_2_10(left, right).extract_12_2(0)); + assertEq(right, Packing.pack_2_10(left, right).extract_12_10(2)); + } + + function testPack(bytes2 left, bytes20 right) external pure { + assertEq(left, Packing.pack_2_20(left, right).extract_22_2(0)); + assertEq(right, Packing.pack_2_20(left, right).extract_22_20(2)); + } + + function testPack(bytes2 left, bytes22 right) external pure { + assertEq(left, Packing.pack_2_22(left, right).extract_24_2(0)); + assertEq(right, Packing.pack_2_22(left, right).extract_24_22(2)); + } + + function testPack(bytes4 left, bytes2 right) external pure { assertEq(left, Packing.pack_4_2(left, right).extract_6_4(0)); assertEq(right, Packing.pack_4_2(left, right).extract_6_2(4)); } - function testPack(bytes4 left, bytes4 right) external { + function testPack(bytes4 left, bytes4 right) external pure { assertEq(left, Packing.pack_4_4(left, right).extract_8_4(0)); assertEq(right, Packing.pack_4_4(left, right).extract_8_4(4)); } - function testPack(bytes4 left, bytes8 right) external { + function testPack(bytes4 left, bytes6 right) external pure { + assertEq(left, Packing.pack_4_6(left, right).extract_10_4(0)); + assertEq(right, Packing.pack_4_6(left, right).extract_10_6(4)); + } + + function testPack(bytes4 left, bytes8 right) external pure { assertEq(left, Packing.pack_4_8(left, right).extract_12_4(0)); assertEq(right, Packing.pack_4_8(left, right).extract_12_8(4)); } - function testPack(bytes4 left, bytes12 right) external { + function testPack(bytes4 left, bytes12 right) external pure { assertEq(left, Packing.pack_4_12(left, right).extract_16_4(0)); assertEq(right, Packing.pack_4_12(left, right).extract_16_12(4)); } - function testPack(bytes4 left, bytes16 right) external { + function testPack(bytes4 left, bytes16 right) external pure { assertEq(left, Packing.pack_4_16(left, right).extract_20_4(0)); assertEq(right, Packing.pack_4_16(left, right).extract_20_16(4)); } - function testPack(bytes4 left, bytes20 right) external { + function testPack(bytes4 left, bytes20 right) external pure { assertEq(left, Packing.pack_4_20(left, right).extract_24_4(0)); assertEq(right, Packing.pack_4_20(left, right).extract_24_20(4)); } - function testPack(bytes4 left, bytes24 right) external { + function testPack(bytes4 left, bytes24 right) external pure { assertEq(left, Packing.pack_4_24(left, right).extract_28_4(0)); assertEq(right, Packing.pack_4_24(left, right).extract_28_24(4)); } - function testPack(bytes4 left, bytes28 right) external { + function testPack(bytes4 left, bytes28 right) external pure { assertEq(left, Packing.pack_4_28(left, right).extract_32_4(0)); assertEq(right, Packing.pack_4_28(left, right).extract_32_28(4)); } - function testPack(bytes6 left, bytes2 right) external { + function testPack(bytes6 left, bytes2 right) external pure { assertEq(left, Packing.pack_6_2(left, right).extract_8_6(0)); assertEq(right, Packing.pack_6_2(left, right).extract_8_2(6)); } - function testPack(bytes6 left, bytes6 right) external { + function testPack(bytes6 left, bytes4 right) external pure { + assertEq(left, Packing.pack_6_4(left, right).extract_10_6(0)); + assertEq(right, Packing.pack_6_4(left, right).extract_10_4(6)); + } + + function testPack(bytes6 left, bytes6 right) external pure { assertEq(left, Packing.pack_6_6(left, right).extract_12_6(0)); assertEq(right, Packing.pack_6_6(left, right).extract_12_6(6)); } - function testPack(bytes8 left, bytes4 right) external { + function testPack(bytes6 left, bytes10 right) external pure { + assertEq(left, Packing.pack_6_10(left, right).extract_16_6(0)); + assertEq(right, Packing.pack_6_10(left, right).extract_16_10(6)); + } + + function testPack(bytes6 left, bytes16 right) external pure { + assertEq(left, Packing.pack_6_16(left, right).extract_22_6(0)); + assertEq(right, Packing.pack_6_16(left, right).extract_22_16(6)); + } + + function testPack(bytes6 left, bytes22 right) external pure { + assertEq(left, Packing.pack_6_22(left, right).extract_28_6(0)); + assertEq(right, Packing.pack_6_22(left, right).extract_28_22(6)); + } + + function testPack(bytes8 left, bytes2 right) external pure { + assertEq(left, Packing.pack_8_2(left, right).extract_10_8(0)); + assertEq(right, Packing.pack_8_2(left, right).extract_10_2(8)); + } + + function testPack(bytes8 left, bytes4 right) external pure { assertEq(left, Packing.pack_8_4(left, right).extract_12_8(0)); assertEq(right, Packing.pack_8_4(left, right).extract_12_4(8)); } - function testPack(bytes8 left, bytes8 right) external { + function testPack(bytes8 left, bytes8 right) external pure { assertEq(left, Packing.pack_8_8(left, right).extract_16_8(0)); assertEq(right, Packing.pack_8_8(left, right).extract_16_8(8)); } - function testPack(bytes8 left, bytes12 right) external { + function testPack(bytes8 left, bytes12 right) external pure { assertEq(left, Packing.pack_8_12(left, right).extract_20_8(0)); assertEq(right, Packing.pack_8_12(left, right).extract_20_12(8)); } - function testPack(bytes8 left, bytes16 right) external { + function testPack(bytes8 left, bytes16 right) external pure { assertEq(left, Packing.pack_8_16(left, right).extract_24_8(0)); assertEq(right, Packing.pack_8_16(left, right).extract_24_16(8)); } - function testPack(bytes8 left, bytes20 right) external { + function testPack(bytes8 left, bytes20 right) external pure { assertEq(left, Packing.pack_8_20(left, right).extract_28_8(0)); assertEq(right, Packing.pack_8_20(left, right).extract_28_20(8)); } - function testPack(bytes8 left, bytes24 right) external { + function testPack(bytes8 left, bytes24 right) external pure { assertEq(left, Packing.pack_8_24(left, right).extract_32_8(0)); assertEq(right, Packing.pack_8_24(left, right).extract_32_24(8)); } - function testPack(bytes12 left, bytes4 right) external { + function testPack(bytes10 left, bytes2 right) external pure { + assertEq(left, Packing.pack_10_2(left, right).extract_12_10(0)); + assertEq(right, Packing.pack_10_2(left, right).extract_12_2(10)); + } + + function testPack(bytes10 left, bytes6 right) external pure { + assertEq(left, Packing.pack_10_6(left, right).extract_16_10(0)); + assertEq(right, Packing.pack_10_6(left, right).extract_16_6(10)); + } + + function testPack(bytes10 left, bytes10 right) external pure { + assertEq(left, Packing.pack_10_10(left, right).extract_20_10(0)); + assertEq(right, Packing.pack_10_10(left, right).extract_20_10(10)); + } + + function testPack(bytes10 left, bytes12 right) external pure { + assertEq(left, Packing.pack_10_12(left, right).extract_22_10(0)); + assertEq(right, Packing.pack_10_12(left, right).extract_22_12(10)); + } + + function testPack(bytes10 left, bytes22 right) external pure { + assertEq(left, Packing.pack_10_22(left, right).extract_32_10(0)); + assertEq(right, Packing.pack_10_22(left, right).extract_32_22(10)); + } + + function testPack(bytes12 left, bytes4 right) external pure { assertEq(left, Packing.pack_12_4(left, right).extract_16_12(0)); assertEq(right, Packing.pack_12_4(left, right).extract_16_4(12)); } - function testPack(bytes12 left, bytes8 right) external { + function testPack(bytes12 left, bytes8 right) external pure { assertEq(left, Packing.pack_12_8(left, right).extract_20_12(0)); assertEq(right, Packing.pack_12_8(left, right).extract_20_8(12)); } - function testPack(bytes12 left, bytes12 right) external { + function testPack(bytes12 left, bytes10 right) external pure { + assertEq(left, Packing.pack_12_10(left, right).extract_22_12(0)); + assertEq(right, Packing.pack_12_10(left, right).extract_22_10(12)); + } + + function testPack(bytes12 left, bytes12 right) external pure { assertEq(left, Packing.pack_12_12(left, right).extract_24_12(0)); assertEq(right, Packing.pack_12_12(left, right).extract_24_12(12)); } - function testPack(bytes12 left, bytes16 right) external { + function testPack(bytes12 left, bytes16 right) external pure { assertEq(left, Packing.pack_12_16(left, right).extract_28_12(0)); assertEq(right, Packing.pack_12_16(left, right).extract_28_16(12)); } - function testPack(bytes12 left, bytes20 right) external { + function testPack(bytes12 left, bytes20 right) external pure { assertEq(left, Packing.pack_12_20(left, right).extract_32_12(0)); assertEq(right, Packing.pack_12_20(left, right).extract_32_20(12)); } - function testPack(bytes16 left, bytes4 right) external { + function testPack(bytes16 left, bytes4 right) external pure { assertEq(left, Packing.pack_16_4(left, right).extract_20_16(0)); assertEq(right, Packing.pack_16_4(left, right).extract_20_4(16)); } - function testPack(bytes16 left, bytes8 right) external { + function testPack(bytes16 left, bytes6 right) external pure { + assertEq(left, Packing.pack_16_6(left, right).extract_22_16(0)); + assertEq(right, Packing.pack_16_6(left, right).extract_22_6(16)); + } + + function testPack(bytes16 left, bytes8 right) external pure { assertEq(left, Packing.pack_16_8(left, right).extract_24_16(0)); assertEq(right, Packing.pack_16_8(left, right).extract_24_8(16)); } - function testPack(bytes16 left, bytes12 right) external { + function testPack(bytes16 left, bytes12 right) external pure { assertEq(left, Packing.pack_16_12(left, right).extract_28_16(0)); assertEq(right, Packing.pack_16_12(left, right).extract_28_12(16)); } - function testPack(bytes16 left, bytes16 right) external { + function testPack(bytes16 left, bytes16 right) external pure { assertEq(left, Packing.pack_16_16(left, right).extract_32_16(0)); assertEq(right, Packing.pack_16_16(left, right).extract_32_16(16)); } - function testPack(bytes20 left, bytes4 right) external { + function testPack(bytes20 left, bytes2 right) external pure { + assertEq(left, Packing.pack_20_2(left, right).extract_22_20(0)); + assertEq(right, Packing.pack_20_2(left, right).extract_22_2(20)); + } + + function testPack(bytes20 left, bytes4 right) external pure { assertEq(left, Packing.pack_20_4(left, right).extract_24_20(0)); assertEq(right, Packing.pack_20_4(left, right).extract_24_4(20)); } - function testPack(bytes20 left, bytes8 right) external { + function testPack(bytes20 left, bytes8 right) external pure { assertEq(left, Packing.pack_20_8(left, right).extract_28_20(0)); assertEq(right, Packing.pack_20_8(left, right).extract_28_8(20)); } - function testPack(bytes20 left, bytes12 right) external { + function testPack(bytes20 left, bytes12 right) external pure { assertEq(left, Packing.pack_20_12(left, right).extract_32_20(0)); assertEq(right, Packing.pack_20_12(left, right).extract_32_12(20)); } - function testPack(bytes24 left, bytes4 right) external { + function testPack(bytes22 left, bytes2 right) external pure { + assertEq(left, Packing.pack_22_2(left, right).extract_24_22(0)); + assertEq(right, Packing.pack_22_2(left, right).extract_24_2(22)); + } + + function testPack(bytes22 left, bytes6 right) external pure { + assertEq(left, Packing.pack_22_6(left, right).extract_28_22(0)); + assertEq(right, Packing.pack_22_6(left, right).extract_28_6(22)); + } + + function testPack(bytes22 left, bytes10 right) external pure { + assertEq(left, Packing.pack_22_10(left, right).extract_32_22(0)); + assertEq(right, Packing.pack_22_10(left, right).extract_32_10(22)); + } + + function testPack(bytes24 left, bytes4 right) external pure { assertEq(left, Packing.pack_24_4(left, right).extract_28_24(0)); assertEq(right, Packing.pack_24_4(left, right).extract_28_4(24)); } - function testPack(bytes24 left, bytes8 right) external { + function testPack(bytes24 left, bytes8 right) external pure { assertEq(left, Packing.pack_24_8(left, right).extract_32_24(0)); assertEq(right, Packing.pack_24_8(left, right).extract_32_8(24)); } - function testPack(bytes28 left, bytes4 right) external { + function testPack(bytes28 left, bytes4 right) external pure { assertEq(left, Packing.pack_28_4(left, right).extract_32_28(0)); assertEq(right, Packing.pack_28_4(left, right).extract_32_4(28)); } - function testReplace(bytes2 container, bytes1 newValue, uint8 offset) external { + function testReplace(bytes2 container, bytes1 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 1)); bytes1 oldValue = container.extract_2_1(offset); @@ -193,7 +298,7 @@ contract PackingTest is Test { assertEq(container, container.replace_2_1(newValue, offset).replace_2_1(oldValue, offset)); } - function testReplace(bytes4 container, bytes1 newValue, uint8 offset) external { + function testReplace(bytes4 container, bytes1 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 3)); bytes1 oldValue = container.extract_4_1(offset); @@ -202,7 +307,7 @@ contract PackingTest is Test { assertEq(container, container.replace_4_1(newValue, offset).replace_4_1(oldValue, offset)); } - function testReplace(bytes4 container, bytes2 newValue, uint8 offset) external { + function testReplace(bytes4 container, bytes2 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 2)); bytes2 oldValue = container.extract_4_2(offset); @@ -211,7 +316,7 @@ contract PackingTest is Test { assertEq(container, container.replace_4_2(newValue, offset).replace_4_2(oldValue, offset)); } - function testReplace(bytes6 container, bytes1 newValue, uint8 offset) external { + function testReplace(bytes6 container, bytes1 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 5)); bytes1 oldValue = container.extract_6_1(offset); @@ -220,7 +325,7 @@ contract PackingTest is Test { assertEq(container, container.replace_6_1(newValue, offset).replace_6_1(oldValue, offset)); } - function testReplace(bytes6 container, bytes2 newValue, uint8 offset) external { + function testReplace(bytes6 container, bytes2 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 4)); bytes2 oldValue = container.extract_6_2(offset); @@ -229,7 +334,7 @@ contract PackingTest is Test { assertEq(container, container.replace_6_2(newValue, offset).replace_6_2(oldValue, offset)); } - function testReplace(bytes6 container, bytes4 newValue, uint8 offset) external { + function testReplace(bytes6 container, bytes4 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 2)); bytes4 oldValue = container.extract_6_4(offset); @@ -238,7 +343,7 @@ contract PackingTest is Test { assertEq(container, container.replace_6_4(newValue, offset).replace_6_4(oldValue, offset)); } - function testReplace(bytes8 container, bytes1 newValue, uint8 offset) external { + function testReplace(bytes8 container, bytes1 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 7)); bytes1 oldValue = container.extract_8_1(offset); @@ -247,7 +352,7 @@ contract PackingTest is Test { assertEq(container, container.replace_8_1(newValue, offset).replace_8_1(oldValue, offset)); } - function testReplace(bytes8 container, bytes2 newValue, uint8 offset) external { + function testReplace(bytes8 container, bytes2 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 6)); bytes2 oldValue = container.extract_8_2(offset); @@ -256,7 +361,7 @@ contract PackingTest is Test { assertEq(container, container.replace_8_2(newValue, offset).replace_8_2(oldValue, offset)); } - function testReplace(bytes8 container, bytes4 newValue, uint8 offset) external { + function testReplace(bytes8 container, bytes4 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 4)); bytes4 oldValue = container.extract_8_4(offset); @@ -265,7 +370,7 @@ contract PackingTest is Test { assertEq(container, container.replace_8_4(newValue, offset).replace_8_4(oldValue, offset)); } - function testReplace(bytes8 container, bytes6 newValue, uint8 offset) external { + function testReplace(bytes8 container, bytes6 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 2)); bytes6 oldValue = container.extract_8_6(offset); @@ -274,7 +379,52 @@ contract PackingTest is Test { assertEq(container, container.replace_8_6(newValue, offset).replace_8_6(oldValue, offset)); } - function testReplace(bytes12 container, bytes1 newValue, uint8 offset) external { + function testReplace(bytes10 container, bytes1 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 9)); + + bytes1 oldValue = container.extract_10_1(offset); + + assertEq(newValue, container.replace_10_1(newValue, offset).extract_10_1(offset)); + assertEq(container, container.replace_10_1(newValue, offset).replace_10_1(oldValue, offset)); + } + + function testReplace(bytes10 container, bytes2 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 8)); + + bytes2 oldValue = container.extract_10_2(offset); + + assertEq(newValue, container.replace_10_2(newValue, offset).extract_10_2(offset)); + assertEq(container, container.replace_10_2(newValue, offset).replace_10_2(oldValue, offset)); + } + + function testReplace(bytes10 container, bytes4 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 6)); + + bytes4 oldValue = container.extract_10_4(offset); + + assertEq(newValue, container.replace_10_4(newValue, offset).extract_10_4(offset)); + assertEq(container, container.replace_10_4(newValue, offset).replace_10_4(oldValue, offset)); + } + + function testReplace(bytes10 container, bytes6 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 4)); + + bytes6 oldValue = container.extract_10_6(offset); + + assertEq(newValue, container.replace_10_6(newValue, offset).extract_10_6(offset)); + assertEq(container, container.replace_10_6(newValue, offset).replace_10_6(oldValue, offset)); + } + + function testReplace(bytes10 container, bytes8 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 2)); + + bytes8 oldValue = container.extract_10_8(offset); + + assertEq(newValue, container.replace_10_8(newValue, offset).extract_10_8(offset)); + assertEq(container, container.replace_10_8(newValue, offset).replace_10_8(oldValue, offset)); + } + + function testReplace(bytes12 container, bytes1 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 11)); bytes1 oldValue = container.extract_12_1(offset); @@ -283,7 +433,7 @@ contract PackingTest is Test { assertEq(container, container.replace_12_1(newValue, offset).replace_12_1(oldValue, offset)); } - function testReplace(bytes12 container, bytes2 newValue, uint8 offset) external { + function testReplace(bytes12 container, bytes2 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 10)); bytes2 oldValue = container.extract_12_2(offset); @@ -292,7 +442,7 @@ contract PackingTest is Test { assertEq(container, container.replace_12_2(newValue, offset).replace_12_2(oldValue, offset)); } - function testReplace(bytes12 container, bytes4 newValue, uint8 offset) external { + function testReplace(bytes12 container, bytes4 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 8)); bytes4 oldValue = container.extract_12_4(offset); @@ -301,7 +451,7 @@ contract PackingTest is Test { assertEq(container, container.replace_12_4(newValue, offset).replace_12_4(oldValue, offset)); } - function testReplace(bytes12 container, bytes6 newValue, uint8 offset) external { + function testReplace(bytes12 container, bytes6 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 6)); bytes6 oldValue = container.extract_12_6(offset); @@ -310,7 +460,7 @@ contract PackingTest is Test { assertEq(container, container.replace_12_6(newValue, offset).replace_12_6(oldValue, offset)); } - function testReplace(bytes12 container, bytes8 newValue, uint8 offset) external { + function testReplace(bytes12 container, bytes8 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 4)); bytes8 oldValue = container.extract_12_8(offset); @@ -319,7 +469,16 @@ contract PackingTest is Test { assertEq(container, container.replace_12_8(newValue, offset).replace_12_8(oldValue, offset)); } - function testReplace(bytes16 container, bytes1 newValue, uint8 offset) external { + function testReplace(bytes12 container, bytes10 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 2)); + + bytes10 oldValue = container.extract_12_10(offset); + + assertEq(newValue, container.replace_12_10(newValue, offset).extract_12_10(offset)); + assertEq(container, container.replace_12_10(newValue, offset).replace_12_10(oldValue, offset)); + } + + function testReplace(bytes16 container, bytes1 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 15)); bytes1 oldValue = container.extract_16_1(offset); @@ -328,7 +487,7 @@ contract PackingTest is Test { assertEq(container, container.replace_16_1(newValue, offset).replace_16_1(oldValue, offset)); } - function testReplace(bytes16 container, bytes2 newValue, uint8 offset) external { + function testReplace(bytes16 container, bytes2 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 14)); bytes2 oldValue = container.extract_16_2(offset); @@ -337,7 +496,7 @@ contract PackingTest is Test { assertEq(container, container.replace_16_2(newValue, offset).replace_16_2(oldValue, offset)); } - function testReplace(bytes16 container, bytes4 newValue, uint8 offset) external { + function testReplace(bytes16 container, bytes4 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 12)); bytes4 oldValue = container.extract_16_4(offset); @@ -346,7 +505,7 @@ contract PackingTest is Test { assertEq(container, container.replace_16_4(newValue, offset).replace_16_4(oldValue, offset)); } - function testReplace(bytes16 container, bytes6 newValue, uint8 offset) external { + function testReplace(bytes16 container, bytes6 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 10)); bytes6 oldValue = container.extract_16_6(offset); @@ -355,7 +514,7 @@ contract PackingTest is Test { assertEq(container, container.replace_16_6(newValue, offset).replace_16_6(oldValue, offset)); } - function testReplace(bytes16 container, bytes8 newValue, uint8 offset) external { + function testReplace(bytes16 container, bytes8 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 8)); bytes8 oldValue = container.extract_16_8(offset); @@ -364,7 +523,16 @@ contract PackingTest is Test { assertEq(container, container.replace_16_8(newValue, offset).replace_16_8(oldValue, offset)); } - function testReplace(bytes16 container, bytes12 newValue, uint8 offset) external { + function testReplace(bytes16 container, bytes10 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 6)); + + bytes10 oldValue = container.extract_16_10(offset); + + assertEq(newValue, container.replace_16_10(newValue, offset).extract_16_10(offset)); + assertEq(container, container.replace_16_10(newValue, offset).replace_16_10(oldValue, offset)); + } + + function testReplace(bytes16 container, bytes12 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 4)); bytes12 oldValue = container.extract_16_12(offset); @@ -373,7 +541,7 @@ contract PackingTest is Test { assertEq(container, container.replace_16_12(newValue, offset).replace_16_12(oldValue, offset)); } - function testReplace(bytes20 container, bytes1 newValue, uint8 offset) external { + function testReplace(bytes20 container, bytes1 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 19)); bytes1 oldValue = container.extract_20_1(offset); @@ -382,7 +550,7 @@ contract PackingTest is Test { assertEq(container, container.replace_20_1(newValue, offset).replace_20_1(oldValue, offset)); } - function testReplace(bytes20 container, bytes2 newValue, uint8 offset) external { + function testReplace(bytes20 container, bytes2 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 18)); bytes2 oldValue = container.extract_20_2(offset); @@ -391,7 +559,7 @@ contract PackingTest is Test { assertEq(container, container.replace_20_2(newValue, offset).replace_20_2(oldValue, offset)); } - function testReplace(bytes20 container, bytes4 newValue, uint8 offset) external { + function testReplace(bytes20 container, bytes4 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 16)); bytes4 oldValue = container.extract_20_4(offset); @@ -400,7 +568,7 @@ contract PackingTest is Test { assertEq(container, container.replace_20_4(newValue, offset).replace_20_4(oldValue, offset)); } - function testReplace(bytes20 container, bytes6 newValue, uint8 offset) external { + function testReplace(bytes20 container, bytes6 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 14)); bytes6 oldValue = container.extract_20_6(offset); @@ -409,7 +577,7 @@ contract PackingTest is Test { assertEq(container, container.replace_20_6(newValue, offset).replace_20_6(oldValue, offset)); } - function testReplace(bytes20 container, bytes8 newValue, uint8 offset) external { + function testReplace(bytes20 container, bytes8 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 12)); bytes8 oldValue = container.extract_20_8(offset); @@ -418,7 +586,16 @@ contract PackingTest is Test { assertEq(container, container.replace_20_8(newValue, offset).replace_20_8(oldValue, offset)); } - function testReplace(bytes20 container, bytes12 newValue, uint8 offset) external { + function testReplace(bytes20 container, bytes10 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 10)); + + bytes10 oldValue = container.extract_20_10(offset); + + assertEq(newValue, container.replace_20_10(newValue, offset).extract_20_10(offset)); + assertEq(container, container.replace_20_10(newValue, offset).replace_20_10(oldValue, offset)); + } + + function testReplace(bytes20 container, bytes12 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 8)); bytes12 oldValue = container.extract_20_12(offset); @@ -427,7 +604,7 @@ contract PackingTest is Test { assertEq(container, container.replace_20_12(newValue, offset).replace_20_12(oldValue, offset)); } - function testReplace(bytes20 container, bytes16 newValue, uint8 offset) external { + function testReplace(bytes20 container, bytes16 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 4)); bytes16 oldValue = container.extract_20_16(offset); @@ -436,7 +613,88 @@ contract PackingTest is Test { assertEq(container, container.replace_20_16(newValue, offset).replace_20_16(oldValue, offset)); } - function testReplace(bytes24 container, bytes1 newValue, uint8 offset) external { + function testReplace(bytes22 container, bytes1 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 21)); + + bytes1 oldValue = container.extract_22_1(offset); + + assertEq(newValue, container.replace_22_1(newValue, offset).extract_22_1(offset)); + assertEq(container, container.replace_22_1(newValue, offset).replace_22_1(oldValue, offset)); + } + + function testReplace(bytes22 container, bytes2 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 20)); + + bytes2 oldValue = container.extract_22_2(offset); + + assertEq(newValue, container.replace_22_2(newValue, offset).extract_22_2(offset)); + assertEq(container, container.replace_22_2(newValue, offset).replace_22_2(oldValue, offset)); + } + + function testReplace(bytes22 container, bytes4 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 18)); + + bytes4 oldValue = container.extract_22_4(offset); + + assertEq(newValue, container.replace_22_4(newValue, offset).extract_22_4(offset)); + assertEq(container, container.replace_22_4(newValue, offset).replace_22_4(oldValue, offset)); + } + + function testReplace(bytes22 container, bytes6 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 16)); + + bytes6 oldValue = container.extract_22_6(offset); + + assertEq(newValue, container.replace_22_6(newValue, offset).extract_22_6(offset)); + assertEq(container, container.replace_22_6(newValue, offset).replace_22_6(oldValue, offset)); + } + + function testReplace(bytes22 container, bytes8 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 14)); + + bytes8 oldValue = container.extract_22_8(offset); + + assertEq(newValue, container.replace_22_8(newValue, offset).extract_22_8(offset)); + assertEq(container, container.replace_22_8(newValue, offset).replace_22_8(oldValue, offset)); + } + + function testReplace(bytes22 container, bytes10 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 12)); + + bytes10 oldValue = container.extract_22_10(offset); + + assertEq(newValue, container.replace_22_10(newValue, offset).extract_22_10(offset)); + assertEq(container, container.replace_22_10(newValue, offset).replace_22_10(oldValue, offset)); + } + + function testReplace(bytes22 container, bytes12 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 10)); + + bytes12 oldValue = container.extract_22_12(offset); + + assertEq(newValue, container.replace_22_12(newValue, offset).extract_22_12(offset)); + assertEq(container, container.replace_22_12(newValue, offset).replace_22_12(oldValue, offset)); + } + + function testReplace(bytes22 container, bytes16 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 6)); + + bytes16 oldValue = container.extract_22_16(offset); + + assertEq(newValue, container.replace_22_16(newValue, offset).extract_22_16(offset)); + assertEq(container, container.replace_22_16(newValue, offset).replace_22_16(oldValue, offset)); + } + + function testReplace(bytes22 container, bytes20 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 2)); + + bytes20 oldValue = container.extract_22_20(offset); + + assertEq(newValue, container.replace_22_20(newValue, offset).extract_22_20(offset)); + assertEq(container, container.replace_22_20(newValue, offset).replace_22_20(oldValue, offset)); + } + + function testReplace(bytes24 container, bytes1 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 23)); bytes1 oldValue = container.extract_24_1(offset); @@ -445,7 +703,7 @@ contract PackingTest is Test { assertEq(container, container.replace_24_1(newValue, offset).replace_24_1(oldValue, offset)); } - function testReplace(bytes24 container, bytes2 newValue, uint8 offset) external { + function testReplace(bytes24 container, bytes2 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 22)); bytes2 oldValue = container.extract_24_2(offset); @@ -454,7 +712,7 @@ contract PackingTest is Test { assertEq(container, container.replace_24_2(newValue, offset).replace_24_2(oldValue, offset)); } - function testReplace(bytes24 container, bytes4 newValue, uint8 offset) external { + function testReplace(bytes24 container, bytes4 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 20)); bytes4 oldValue = container.extract_24_4(offset); @@ -463,7 +721,7 @@ contract PackingTest is Test { assertEq(container, container.replace_24_4(newValue, offset).replace_24_4(oldValue, offset)); } - function testReplace(bytes24 container, bytes6 newValue, uint8 offset) external { + function testReplace(bytes24 container, bytes6 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 18)); bytes6 oldValue = container.extract_24_6(offset); @@ -472,7 +730,7 @@ contract PackingTest is Test { assertEq(container, container.replace_24_6(newValue, offset).replace_24_6(oldValue, offset)); } - function testReplace(bytes24 container, bytes8 newValue, uint8 offset) external { + function testReplace(bytes24 container, bytes8 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 16)); bytes8 oldValue = container.extract_24_8(offset); @@ -481,7 +739,16 @@ contract PackingTest is Test { assertEq(container, container.replace_24_8(newValue, offset).replace_24_8(oldValue, offset)); } - function testReplace(bytes24 container, bytes12 newValue, uint8 offset) external { + function testReplace(bytes24 container, bytes10 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 14)); + + bytes10 oldValue = container.extract_24_10(offset); + + assertEq(newValue, container.replace_24_10(newValue, offset).extract_24_10(offset)); + assertEq(container, container.replace_24_10(newValue, offset).replace_24_10(oldValue, offset)); + } + + function testReplace(bytes24 container, bytes12 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 12)); bytes12 oldValue = container.extract_24_12(offset); @@ -490,7 +757,7 @@ contract PackingTest is Test { assertEq(container, container.replace_24_12(newValue, offset).replace_24_12(oldValue, offset)); } - function testReplace(bytes24 container, bytes16 newValue, uint8 offset) external { + function testReplace(bytes24 container, bytes16 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 8)); bytes16 oldValue = container.extract_24_16(offset); @@ -499,7 +766,7 @@ contract PackingTest is Test { assertEq(container, container.replace_24_16(newValue, offset).replace_24_16(oldValue, offset)); } - function testReplace(bytes24 container, bytes20 newValue, uint8 offset) external { + function testReplace(bytes24 container, bytes20 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 4)); bytes20 oldValue = container.extract_24_20(offset); @@ -508,7 +775,16 @@ contract PackingTest is Test { assertEq(container, container.replace_24_20(newValue, offset).replace_24_20(oldValue, offset)); } - function testReplace(bytes28 container, bytes1 newValue, uint8 offset) external { + function testReplace(bytes24 container, bytes22 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 2)); + + bytes22 oldValue = container.extract_24_22(offset); + + assertEq(newValue, container.replace_24_22(newValue, offset).extract_24_22(offset)); + assertEq(container, container.replace_24_22(newValue, offset).replace_24_22(oldValue, offset)); + } + + function testReplace(bytes28 container, bytes1 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 27)); bytes1 oldValue = container.extract_28_1(offset); @@ -517,7 +793,7 @@ contract PackingTest is Test { assertEq(container, container.replace_28_1(newValue, offset).replace_28_1(oldValue, offset)); } - function testReplace(bytes28 container, bytes2 newValue, uint8 offset) external { + function testReplace(bytes28 container, bytes2 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 26)); bytes2 oldValue = container.extract_28_2(offset); @@ -526,7 +802,7 @@ contract PackingTest is Test { assertEq(container, container.replace_28_2(newValue, offset).replace_28_2(oldValue, offset)); } - function testReplace(bytes28 container, bytes4 newValue, uint8 offset) external { + function testReplace(bytes28 container, bytes4 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 24)); bytes4 oldValue = container.extract_28_4(offset); @@ -535,7 +811,7 @@ contract PackingTest is Test { assertEq(container, container.replace_28_4(newValue, offset).replace_28_4(oldValue, offset)); } - function testReplace(bytes28 container, bytes6 newValue, uint8 offset) external { + function testReplace(bytes28 container, bytes6 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 22)); bytes6 oldValue = container.extract_28_6(offset); @@ -544,7 +820,7 @@ contract PackingTest is Test { assertEq(container, container.replace_28_6(newValue, offset).replace_28_6(oldValue, offset)); } - function testReplace(bytes28 container, bytes8 newValue, uint8 offset) external { + function testReplace(bytes28 container, bytes8 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 20)); bytes8 oldValue = container.extract_28_8(offset); @@ -553,7 +829,16 @@ contract PackingTest is Test { assertEq(container, container.replace_28_8(newValue, offset).replace_28_8(oldValue, offset)); } - function testReplace(bytes28 container, bytes12 newValue, uint8 offset) external { + function testReplace(bytes28 container, bytes10 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 18)); + + bytes10 oldValue = container.extract_28_10(offset); + + assertEq(newValue, container.replace_28_10(newValue, offset).extract_28_10(offset)); + assertEq(container, container.replace_28_10(newValue, offset).replace_28_10(oldValue, offset)); + } + + function testReplace(bytes28 container, bytes12 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 16)); bytes12 oldValue = container.extract_28_12(offset); @@ -562,7 +847,7 @@ contract PackingTest is Test { assertEq(container, container.replace_28_12(newValue, offset).replace_28_12(oldValue, offset)); } - function testReplace(bytes28 container, bytes16 newValue, uint8 offset) external { + function testReplace(bytes28 container, bytes16 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 12)); bytes16 oldValue = container.extract_28_16(offset); @@ -571,7 +856,7 @@ contract PackingTest is Test { assertEq(container, container.replace_28_16(newValue, offset).replace_28_16(oldValue, offset)); } - function testReplace(bytes28 container, bytes20 newValue, uint8 offset) external { + function testReplace(bytes28 container, bytes20 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 8)); bytes20 oldValue = container.extract_28_20(offset); @@ -580,7 +865,16 @@ contract PackingTest is Test { assertEq(container, container.replace_28_20(newValue, offset).replace_28_20(oldValue, offset)); } - function testReplace(bytes28 container, bytes24 newValue, uint8 offset) external { + function testReplace(bytes28 container, bytes22 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 6)); + + bytes22 oldValue = container.extract_28_22(offset); + + assertEq(newValue, container.replace_28_22(newValue, offset).extract_28_22(offset)); + assertEq(container, container.replace_28_22(newValue, offset).replace_28_22(oldValue, offset)); + } + + function testReplace(bytes28 container, bytes24 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 4)); bytes24 oldValue = container.extract_28_24(offset); @@ -589,7 +883,7 @@ contract PackingTest is Test { assertEq(container, container.replace_28_24(newValue, offset).replace_28_24(oldValue, offset)); } - function testReplace(bytes32 container, bytes1 newValue, uint8 offset) external { + function testReplace(bytes32 container, bytes1 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 31)); bytes1 oldValue = container.extract_32_1(offset); @@ -598,7 +892,7 @@ contract PackingTest is Test { assertEq(container, container.replace_32_1(newValue, offset).replace_32_1(oldValue, offset)); } - function testReplace(bytes32 container, bytes2 newValue, uint8 offset) external { + function testReplace(bytes32 container, bytes2 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 30)); bytes2 oldValue = container.extract_32_2(offset); @@ -607,7 +901,7 @@ contract PackingTest is Test { assertEq(container, container.replace_32_2(newValue, offset).replace_32_2(oldValue, offset)); } - function testReplace(bytes32 container, bytes4 newValue, uint8 offset) external { + function testReplace(bytes32 container, bytes4 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 28)); bytes4 oldValue = container.extract_32_4(offset); @@ -616,7 +910,7 @@ contract PackingTest is Test { assertEq(container, container.replace_32_4(newValue, offset).replace_32_4(oldValue, offset)); } - function testReplace(bytes32 container, bytes6 newValue, uint8 offset) external { + function testReplace(bytes32 container, bytes6 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 26)); bytes6 oldValue = container.extract_32_6(offset); @@ -625,7 +919,7 @@ contract PackingTest is Test { assertEq(container, container.replace_32_6(newValue, offset).replace_32_6(oldValue, offset)); } - function testReplace(bytes32 container, bytes8 newValue, uint8 offset) external { + function testReplace(bytes32 container, bytes8 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 24)); bytes8 oldValue = container.extract_32_8(offset); @@ -634,7 +928,16 @@ contract PackingTest is Test { assertEq(container, container.replace_32_8(newValue, offset).replace_32_8(oldValue, offset)); } - function testReplace(bytes32 container, bytes12 newValue, uint8 offset) external { + function testReplace(bytes32 container, bytes10 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 22)); + + bytes10 oldValue = container.extract_32_10(offset); + + assertEq(newValue, container.replace_32_10(newValue, offset).extract_32_10(offset)); + assertEq(container, container.replace_32_10(newValue, offset).replace_32_10(oldValue, offset)); + } + + function testReplace(bytes32 container, bytes12 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 20)); bytes12 oldValue = container.extract_32_12(offset); @@ -643,7 +946,7 @@ contract PackingTest is Test { assertEq(container, container.replace_32_12(newValue, offset).replace_32_12(oldValue, offset)); } - function testReplace(bytes32 container, bytes16 newValue, uint8 offset) external { + function testReplace(bytes32 container, bytes16 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 16)); bytes16 oldValue = container.extract_32_16(offset); @@ -652,7 +955,7 @@ contract PackingTest is Test { assertEq(container, container.replace_32_16(newValue, offset).replace_32_16(oldValue, offset)); } - function testReplace(bytes32 container, bytes20 newValue, uint8 offset) external { + function testReplace(bytes32 container, bytes20 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 12)); bytes20 oldValue = container.extract_32_20(offset); @@ -661,7 +964,16 @@ contract PackingTest is Test { assertEq(container, container.replace_32_20(newValue, offset).replace_32_20(oldValue, offset)); } - function testReplace(bytes32 container, bytes24 newValue, uint8 offset) external { + function testReplace(bytes32 container, bytes22 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 10)); + + bytes22 oldValue = container.extract_32_22(offset); + + assertEq(newValue, container.replace_32_22(newValue, offset).extract_32_22(offset)); + assertEq(container, container.replace_32_22(newValue, offset).replace_32_22(oldValue, offset)); + } + + function testReplace(bytes32 container, bytes24 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 8)); bytes24 oldValue = container.extract_32_24(offset); @@ -670,7 +982,7 @@ contract PackingTest is Test { assertEq(container, container.replace_32_24(newValue, offset).replace_32_24(oldValue, offset)); } - function testReplace(bytes32 container, bytes28 newValue, uint8 offset) external { + function testReplace(bytes32 container, bytes28 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 4)); bytes28 oldValue = container.extract_32_28(offset); From bcdfa848a6abb37216df861573d9576636e77824 Mon Sep 17 00:00:00 2001 From: Sam Bugs <101145325+0xsambugs@users.noreply.github.com> Date: Fri, 25 Oct 2024 12:26:35 -0300 Subject: [PATCH 44/84] Remove unused import in `SafeERC20` (#5278) --- contracts/token/ERC20/utils/SafeERC20.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/contracts/token/ERC20/utils/SafeERC20.sol b/contracts/token/ERC20/utils/SafeERC20.sol index eb2f903fbed..a77907b4c33 100644 --- a/contracts/token/ERC20/utils/SafeERC20.sol +++ b/contracts/token/ERC20/utils/SafeERC20.sol @@ -5,7 +5,6 @@ pragma solidity ^0.8.20; import {IERC20} from "../IERC20.sol"; import {IERC1363} from "../../../interfaces/IERC1363.sol"; -import {Address} from "../../../utils/Address.sol"; /** * @title SafeERC20 From f96237308f6d25ef69bd4c153e5651794651011f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Mon, 28 Oct 2024 10:33:25 -0600 Subject: [PATCH 45/84] Update forge and change visibility in fuzz tests (#5103) Co-authored-by: cairo --- lib/forge-std | 2 +- scripts/generate/templates/Checkpoints.t.js | 2 +- .../generate/templates/SlotDerivation.t.js | 8 ++--- test/governance/Governor.t.sol | 8 +++-- test/proxy/Clones.t.sol | 6 ++-- test/utils/Arrays.t.sol | 6 ++-- test/utils/Base64.t.sol | 4 +-- test/utils/Create2.t.sol | 2 +- test/utils/ShortStrings.t.sol | 12 +++---- test/utils/SlotDerivation.t.sol | 28 ++++++++-------- test/utils/Strings.t.sol | 8 ++--- test/utils/cryptography/P256.t.sol | 4 +-- test/utils/math/Math.t.sol | 32 +++++++++---------- test/utils/math/SignedMath.t.sol | 14 ++++---- test/utils/structs/Checkpoints.t.sol | 6 ++-- test/utils/structs/Heap.t.sol | 2 +- 16 files changed, 74 insertions(+), 70 deletions(-) diff --git a/lib/forge-std b/lib/forge-std index 8f24d6b04c9..1eea5bae12a 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit 8f24d6b04c92975e0795b5868aa0d783251cdeaa +Subproject commit 1eea5bae12ae557d589f9f0f0edae2faa47cb262 diff --git a/scripts/generate/templates/Checkpoints.t.js b/scripts/generate/templates/Checkpoints.t.js index edd2e9f98aa..8e178adfac6 100644 --- a/scripts/generate/templates/Checkpoints.t.js +++ b/scripts/generate/templates/Checkpoints.t.js @@ -36,7 +36,7 @@ function _prepareKeys(${opts.keyTypeName}[] memory keys, ${opts.keyTypeName} max } } -function _assertLatestCheckpoint(bool exist, ${opts.keyTypeName} key, ${opts.valueTypeName} value) internal { +function _assertLatestCheckpoint(bool exist, ${opts.keyTypeName} key, ${opts.valueTypeName} value) internal view { (bool _exist, ${opts.keyTypeName} _key, ${opts.valueTypeName} _value) = _ckpts.latestCheckpoint(); assertEq(_exist, exist); assertEq(_key, key); diff --git a/scripts/generate/templates/SlotDerivation.t.js b/scripts/generate/templates/SlotDerivation.t.js index f03e1fc2598..824af079a14 100644 --- a/scripts/generate/templates/SlotDerivation.t.js +++ b/scripts/generate/templates/SlotDerivation.t.js @@ -45,7 +45,7 @@ function _assertDeriveArray(uint256 length, uint256 offset) public { const mapping = ({ type, name }) => `\ mapping(${type} => bytes) private _${type}Mapping; -function testSymbolicDeriveMapping${name}(${type} key) public { +function testSymbolicDeriveMapping${name}(${type} key) public view { bytes32 baseSlot; assembly { baseSlot := _${type}Mapping.slot @@ -76,15 +76,15 @@ function testSymbolicDeriveMapping${name}Dirty(bytes32 dirtyKey) public { const boundedMapping = ({ type, name }) => `\ mapping(${type} => bytes) private _${type}Mapping; -function testDeriveMapping${name}(${type} memory key) public { +function testDeriveMapping${name}(${type} memory key) public view { _assertDeriveMapping${name}(key); } -function symbolicDeriveMapping${name}() public { +function symbolicDeriveMapping${name}() public view { _assertDeriveMapping${name}(svm.create${name}(256, "DeriveMapping${name}Input")); } -function _assertDeriveMapping${name}(${type} memory key) internal { +function _assertDeriveMapping${name}(${type} memory key) internal view { bytes32 baseSlot; assembly { baseSlot := _${type}Mapping.slot diff --git a/test/governance/Governor.t.sol b/test/governance/Governor.t.sol index 958461abb95..66b684d26e4 100644 --- a/test/governance/Governor.t.sol +++ b/test/governance/Governor.t.sol @@ -9,7 +9,11 @@ import {Governor} from "@openzeppelin/contracts/governance/Governor.sol"; contract GovernorInternalTest is Test, Governor { constructor() Governor("") {} - function testValidDescriptionForProposer(string memory description, address proposer, bool includeProposer) public { + function testValidDescriptionForProposer( + string memory description, + address proposer, + bool includeProposer + ) public view { if (includeProposer) { description = string.concat(description, "#proposer=", Strings.toHexString(proposer)); } @@ -20,7 +24,7 @@ contract GovernorInternalTest is Test, Governor { string memory description, address commitProposer, address actualProposer - ) public { + ) public view { vm.assume(commitProposer != actualProposer); description = string.concat(description, "#proposer=", Strings.toHexString(commitProposer)); assertFalse(_isValidDescriptionForProposer(actualProposer, description)); diff --git a/test/proxy/Clones.t.sol b/test/proxy/Clones.t.sol index e589ba90678..5da6d56d5a6 100644 --- a/test/proxy/Clones.t.sol +++ b/test/proxy/Clones.t.sol @@ -10,7 +10,7 @@ contract ClonesTest is Test { return 42; } - function testSymbolicPredictDeterministicAddressSpillage(address implementation, bytes32 salt) public { + function testSymbolicPredictDeterministicAddressSpillage(address implementation, bytes32 salt) public view { address predicted = Clones.predictDeterministicAddress(implementation, salt); bytes32 spillage; assembly ("memory-safe") { @@ -23,7 +23,7 @@ contract ClonesTest is Test { address implementation, bytes32 salt, bytes memory args - ) public { + ) public view { vm.assume(args.length < 0xbfd3); address predicted = Clones.predictDeterministicAddressWithImmutableArgs(implementation, args, salt); @@ -59,7 +59,7 @@ contract ClonesTest is Test { assertEq(ClonesTest(cloneDirty).getNumber(), this.getNumber()); } - function testPredictDeterministicAddressDirty(bytes32 salt) external { + function testPredictDeterministicAddressDirty(bytes32 salt) external view { address predictClean = Clones.predictDeterministicAddress(address(this), salt); address predictDirty = Clones.predictDeterministicAddress(_dirty(address(this)), salt); diff --git a/test/utils/Arrays.t.sol b/test/utils/Arrays.t.sol index 09c7b66b6d6..e45d29c919d 100644 --- a/test/utils/Arrays.t.sol +++ b/test/utils/Arrays.t.sol @@ -7,12 +7,12 @@ import {SymTest} from "halmos-cheatcodes/SymTest.sol"; import {Arrays} from "@openzeppelin/contracts/utils/Arrays.sol"; contract ArraysTest is Test, SymTest { - function testSort(uint256[] memory values) public { + function testSort(uint256[] memory values) public pure { Arrays.sort(values); _assertSort(values); } - function symbolicSort() public { + function symbolicSort() public pure { uint256[] memory values = new uint256[](3); for (uint256 i = 0; i < 3; i++) { values[i] = svm.createUint256("arrayElement"); @@ -23,7 +23,7 @@ contract ArraysTest is Test, SymTest { /// Asserts - function _assertSort(uint256[] memory values) internal { + function _assertSort(uint256[] memory values) internal pure { for (uint256 i = 1; i < values.length; ++i) { assertLe(values[i - 1], values[i]); } diff --git a/test/utils/Base64.t.sol b/test/utils/Base64.t.sol index 021ae03af09..b8aa7ac6121 100644 --- a/test/utils/Base64.t.sol +++ b/test/utils/Base64.t.sol @@ -6,11 +6,11 @@ import {Test} from "forge-std/Test.sol"; import {Base64} from "@openzeppelin/contracts/utils/Base64.sol"; contract Base64Test is Test { - function testEncode(bytes memory input) external { + function testEncode(bytes memory input) external pure { assertEq(Base64.encode(input), vm.toBase64(input)); } - function testEncodeURL(bytes memory input) external { + function testEncodeURL(bytes memory input) external pure { assertEq(Base64.encodeURL(input), _removePadding(vm.toBase64URL(input))); } diff --git a/test/utils/Create2.t.sol b/test/utils/Create2.t.sol index 6cc037a3b3b..b73db9f9a25 100644 --- a/test/utils/Create2.t.sol +++ b/test/utils/Create2.t.sol @@ -6,7 +6,7 @@ import {Test} from "forge-std/Test.sol"; import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; contract Create2Test is Test { - function testSymbolicComputeAddressSpillage(bytes32 salt, bytes32 bytecodeHash, address deployer) public { + function testSymbolicComputeAddressSpillage(bytes32 salt, bytes32 bytecodeHash, address deployer) public pure { address predicted = Create2.computeAddress(salt, bytecodeHash, deployer); bytes32 spillage; assembly ("memory-safe") { diff --git a/test/utils/ShortStrings.t.sol b/test/utils/ShortStrings.t.sol index 4aeafd721a8..80313bf7cd1 100644 --- a/test/utils/ShortStrings.t.sol +++ b/test/utils/ShortStrings.t.sol @@ -10,12 +10,12 @@ import {ShortStrings, ShortString} from "@openzeppelin/contracts/utils/ShortStri contract ShortStringsTest is Test, SymTest { string _fallback; - function testRoundtripShort(string memory input) external { + function testRoundtripShort(string memory input) external pure { vm.assume(_isShort(input)); _assertRoundtripShort(input); } - function symbolicRoundtripShort() external { + function symbolicRoundtripShort() external pure { string memory input = svm.createString(31, "RoundtripShortInput"); _assertRoundtripShort(input); } @@ -41,12 +41,12 @@ contract ShortStringsTest is Test, SymTest { _assertRevertLong(input); } - function testLengthShort(string memory input) external { + function testLengthShort(string memory input) external pure { vm.assume(_isShort(input)); _assertLengthShort(input); } - function symbolicLengthShort() external { + function symbolicLengthShort() external pure { string memory input = svm.createString(31, "LengthShortInput"); _assertLengthShort(input); } @@ -66,7 +66,7 @@ contract ShortStringsTest is Test, SymTest { /// Assertions - function _assertRoundtripShort(string memory input) internal { + function _assertRoundtripShort(string memory input) internal pure { ShortString short = ShortStrings.toShortString(input); string memory output = ShortStrings.toString(short); assertEq(input, output); @@ -84,7 +84,7 @@ contract ShortStringsTest is Test, SymTest { this.toShortString(input); } - function _assertLengthShort(string memory input) internal { + function _assertLengthShort(string memory input) internal pure { ShortString short = ShortStrings.toShortString(input); uint256 shortLength = ShortStrings.byteLength(short); uint256 inputLength = bytes(input).length; diff --git a/test/utils/SlotDerivation.t.sol b/test/utils/SlotDerivation.t.sol index 4021f0f8796..a0846e33212 100644 --- a/test/utils/SlotDerivation.t.sol +++ b/test/utils/SlotDerivation.t.sol @@ -42,7 +42,7 @@ contract SlotDerivationTest is Test, SymTest { mapping(address => bytes) private _addressMapping; - function testSymbolicDeriveMappingAddress(address key) public { + function testSymbolicDeriveMappingAddress(address key) public view { bytes32 baseSlot; assembly { baseSlot := _addressMapping.slot @@ -59,7 +59,7 @@ contract SlotDerivationTest is Test, SymTest { mapping(bool => bytes) private _boolMapping; - function testSymbolicDeriveMappingBoolean(bool key) public { + function testSymbolicDeriveMappingBoolean(bool key) public view { bytes32 baseSlot; assembly { baseSlot := _boolMapping.slot @@ -76,7 +76,7 @@ contract SlotDerivationTest is Test, SymTest { mapping(bytes32 => bytes) private _bytes32Mapping; - function testSymbolicDeriveMappingBytes32(bytes32 key) public { + function testSymbolicDeriveMappingBytes32(bytes32 key) public view { bytes32 baseSlot; assembly { baseSlot := _bytes32Mapping.slot @@ -93,7 +93,7 @@ contract SlotDerivationTest is Test, SymTest { mapping(bytes4 => bytes) private _bytes4Mapping; - function testSymbolicDeriveMappingBytes4(bytes4 key) public { + function testSymbolicDeriveMappingBytes4(bytes4 key) public view { bytes32 baseSlot; assembly { baseSlot := _bytes4Mapping.slot @@ -110,7 +110,7 @@ contract SlotDerivationTest is Test, SymTest { mapping(uint256 => bytes) private _uint256Mapping; - function testSymbolicDeriveMappingUint256(uint256 key) public { + function testSymbolicDeriveMappingUint256(uint256 key) public view { bytes32 baseSlot; assembly { baseSlot := _uint256Mapping.slot @@ -127,7 +127,7 @@ contract SlotDerivationTest is Test, SymTest { mapping(uint32 => bytes) private _uint32Mapping; - function testSymbolicDeriveMappingUint32(uint32 key) public { + function testSymbolicDeriveMappingUint32(uint32 key) public view { bytes32 baseSlot; assembly { baseSlot := _uint32Mapping.slot @@ -144,7 +144,7 @@ contract SlotDerivationTest is Test, SymTest { mapping(int256 => bytes) private _int256Mapping; - function testSymbolicDeriveMappingInt256(int256 key) public { + function testSymbolicDeriveMappingInt256(int256 key) public view { bytes32 baseSlot; assembly { baseSlot := _int256Mapping.slot @@ -161,7 +161,7 @@ contract SlotDerivationTest is Test, SymTest { mapping(int32 => bytes) private _int32Mapping; - function testSymbolicDeriveMappingInt32(int32 key) public { + function testSymbolicDeriveMappingInt32(int32 key) public view { bytes32 baseSlot; assembly { baseSlot := _int32Mapping.slot @@ -178,15 +178,15 @@ contract SlotDerivationTest is Test, SymTest { mapping(string => bytes) private _stringMapping; - function testDeriveMappingString(string memory key) public { + function testDeriveMappingString(string memory key) public view { _assertDeriveMappingString(key); } - function symbolicDeriveMappingString() public { + function symbolicDeriveMappingString() public view { _assertDeriveMappingString(svm.createString(256, "DeriveMappingStringInput")); } - function _assertDeriveMappingString(string memory key) internal { + function _assertDeriveMappingString(string memory key) internal view { bytes32 baseSlot; assembly { baseSlot := _stringMapping.slot @@ -203,15 +203,15 @@ contract SlotDerivationTest is Test, SymTest { mapping(bytes => bytes) private _bytesMapping; - function testDeriveMappingBytes(bytes memory key) public { + function testDeriveMappingBytes(bytes memory key) public view { _assertDeriveMappingBytes(key); } - function symbolicDeriveMappingBytes() public { + function symbolicDeriveMappingBytes() public view { _assertDeriveMappingBytes(svm.createBytes(256, "DeriveMappingBytesInput")); } - function _assertDeriveMappingBytes(bytes memory key) internal { + function _assertDeriveMappingBytes(bytes memory key) internal view { bytes32 baseSlot; assembly { baseSlot := _bytesMapping.slot diff --git a/test/utils/Strings.t.sol b/test/utils/Strings.t.sol index b3eb67a5cd2..45e11342916 100644 --- a/test/utils/Strings.t.sol +++ b/test/utils/Strings.t.sol @@ -9,19 +9,19 @@ import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; contract StringsTest is Test { using Strings for *; - function testParse(uint256 value) external { + function testParse(uint256 value) external pure { assertEq(value, value.toString().parseUint()); } - function testParseSigned(int256 value) external { + function testParseSigned(int256 value) external pure { assertEq(value, value.toStringSigned().parseInt()); } - function testParseHex(uint256 value) external { + function testParseHex(uint256 value) external pure { assertEq(value, value.toHexString().parseHexUint()); } - function testParseChecksumHex(address value) external { + function testParseChecksumHex(address value) external pure { assertEq(value, value.toChecksumHexString().parseAddress()); } } diff --git a/test/utils/cryptography/P256.t.sol b/test/utils/cryptography/P256.t.sol index 0c9b2c78a04..d70cdf1bf2d 100644 --- a/test/utils/cryptography/P256.t.sol +++ b/test/utils/cryptography/P256.t.sol @@ -9,7 +9,7 @@ import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; contract P256Test is Test { /// forge-config: default.fuzz.runs = 512 - function testVerify(bytes32 digest, uint256 seed) public { + function testVerify(bytes32 digest, uint256 seed) public view { uint256 privateKey = _asPrivateKey(seed); (uint256 x, uint256 y) = vm.publicKeyP256(privateKey); @@ -20,7 +20,7 @@ contract P256Test is Test { } /// forge-config: default.fuzz.runs = 512 - function testRecover(bytes32 digest, uint256 seed) public { + function testRecover(bytes32 digest, uint256 seed) public view { uint256 privateKey = _asPrivateKey(seed); (uint256 x, uint256 y) = vm.publicKeyP256(privateKey); diff --git a/test/utils/math/Math.t.sol b/test/utils/math/Math.t.sol index 3d4932eea7a..6c122d66c3f 100644 --- a/test/utils/math/Math.t.sol +++ b/test/utils/math/Math.t.sol @@ -7,18 +7,18 @@ import {Test, stdError} from "forge-std/Test.sol"; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; contract MathTest is Test { - function testSymbolicTernary(bool f, uint256 a, uint256 b) public { + function testSymbolicTernary(bool f, uint256 a, uint256 b) public pure { assertEq(Math.ternary(f, a, b), f ? a : b); } // MIN & MAX - function testSymbolicMinMax(uint256 a, uint256 b) public { + function testSymbolicMinMax(uint256 a, uint256 b) public pure { assertEq(Math.min(a, b), a < b ? a : b); assertEq(Math.max(a, b), a > b ? a : b); } // CEILDIV - function testCeilDiv(uint256 a, uint256 b) public { + function testCeilDiv(uint256 a, uint256 b) public pure { vm.assume(b > 0); uint256 result = Math.ceilDiv(a, b); @@ -35,7 +35,7 @@ contract MathTest is Test { } // SQRT - function testSqrt(uint256 input, uint8 r) public { + function testSqrt(uint256 input, uint8 r) public pure { Math.Rounding rounding = _asRounding(r); uint256 result = Math.sqrt(input, rounding); @@ -66,31 +66,31 @@ contract MathTest is Test { } // INV - function testInvMod(uint256 value, uint256 p) public { + function testInvMod(uint256 value, uint256 p) public pure { _testInvMod(value, p, true); } - function testInvMod2(uint256 seed) public { + function testInvMod2(uint256 seed) public pure { uint256 p = 2; // prime _testInvMod(bound(seed, 1, p - 1), p, false); } - function testInvMod17(uint256 seed) public { + function testInvMod17(uint256 seed) public pure { uint256 p = 17; // prime _testInvMod(bound(seed, 1, p - 1), p, false); } - function testInvMod65537(uint256 seed) public { + function testInvMod65537(uint256 seed) public pure { uint256 p = 65537; // prime _testInvMod(bound(seed, 1, p - 1), p, false); } - function testInvModP256(uint256 seed) public { + function testInvModP256(uint256 seed) public pure { uint256 p = 0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff; // prime _testInvMod(bound(seed, 1, p - 1), p, false); } - function _testInvMod(uint256 value, uint256 p, bool allowZero) private { + function _testInvMod(uint256 value, uint256 p, bool allowZero) private pure { uint256 inverse = Math.invMod(value, p); if (inverse != 0) { assertEq(mulmod(value, inverse, p), 1); @@ -101,7 +101,7 @@ contract MathTest is Test { } // LOG2 - function testLog2(uint256 input, uint8 r) public { + function testLog2(uint256 input, uint8 r) public pure { Math.Rounding rounding = _asRounding(r); uint256 result = Math.log2(input, rounding); @@ -128,7 +128,7 @@ contract MathTest is Test { } // LOG10 - function testLog10(uint256 input, uint8 r) public { + function testLog10(uint256 input, uint8 r) public pure { Math.Rounding rounding = _asRounding(r); uint256 result = Math.log10(input, rounding); @@ -155,7 +155,7 @@ contract MathTest is Test { } // LOG256 - function testLog256(uint256 input, uint8 r) public { + function testLog256(uint256 input, uint8 r) public pure { Math.Rounding rounding = _asRounding(r); uint256 result = Math.log256(input, rounding); @@ -182,7 +182,7 @@ contract MathTest is Test { } // MULDIV - function testMulDiv(uint256 x, uint256 y, uint256 d) public { + function testMulDiv(uint256 x, uint256 y, uint256 d) public pure { // Full precision for x * y (uint256 xyHi, uint256 xyLo) = _mulHighLow(x, y); @@ -225,7 +225,7 @@ contract MathTest is Test { assertEq(result, _nativeModExp(b, e, m)); } - function testTryModExp(uint256 b, uint256 e, uint256 m) public { + function testTryModExp(uint256 b, uint256 e, uint256 m) public view { (bool success, uint256 result) = Math.tryModExp(b, e, m); assertEq(success, m != 0); if (success) { @@ -247,7 +247,7 @@ contract MathTest is Test { assertEq(res, _nativeModExp(b, e, m)); } - function testTryModExpMemory(uint256 b, uint256 e, uint256 m) public { + function testTryModExpMemory(uint256 b, uint256 e, uint256 m) public view { (bool success, bytes memory result) = Math.tryModExp( abi.encodePacked(b), abi.encodePacked(e), diff --git a/test/utils/math/SignedMath.t.sol b/test/utils/math/SignedMath.t.sol index bbad109b71e..98a40a13998 100644 --- a/test/utils/math/SignedMath.t.sol +++ b/test/utils/math/SignedMath.t.sol @@ -8,18 +8,18 @@ import {Math} from "../../../contracts/utils/math/Math.sol"; import {SignedMath} from "../../../contracts/utils/math/SignedMath.sol"; contract SignedMathTest is Test { - function testSymbolicTernary(bool f, int256 a, int256 b) public { + function testSymbolicTernary(bool f, int256 a, int256 b) public pure { assertEq(SignedMath.ternary(f, a, b), f ? a : b); } // MIN & MAX - function testSymbolicMinMax(int256 a, int256 b) public { + function testSymbolicMinMax(int256 a, int256 b) public pure { assertEq(SignedMath.min(a, b), a < b ? a : b); assertEq(SignedMath.max(a, b), a > b ? a : b); } // MIN - function testSymbolicMin(int256 a, int256 b) public { + function testSymbolicMin(int256 a, int256 b) public pure { int256 result = SignedMath.min(a, b); assertLe(result, a); @@ -28,7 +28,7 @@ contract SignedMathTest is Test { } // MAX - function testSymbolicMax(int256 a, int256 b) public { + function testSymbolicMax(int256 a, int256 b) public pure { int256 result = SignedMath.max(a, b); assertGe(result, a); @@ -38,7 +38,7 @@ contract SignedMathTest is Test { // AVERAGE // 1. simple test, not full int256 range - function testAverage1(int256 a, int256 b) public { + function testAverage1(int256 a, int256 b) public pure { a = bound(a, type(int256).min / 2, type(int256).max / 2); b = bound(b, type(int256).min / 2, type(int256).max / 2); @@ -48,7 +48,7 @@ contract SignedMathTest is Test { } // 2. more complex test, full int256 range - function testAverage2(int256 a, int256 b) public { + function testAverage2(int256 a, int256 b) public pure { (int256 result, int256 min, int256 max) = ( SignedMath.average(a, b), SignedMath.min(a, b), @@ -69,7 +69,7 @@ contract SignedMathTest is Test { } // ABS - function testSymbolicAbs(int256 a) public { + function testSymbolicAbs(int256 a) public pure { uint256 result = SignedMath.abs(a); unchecked { diff --git a/test/utils/structs/Checkpoints.t.sol b/test/utils/structs/Checkpoints.t.sol index 1f4b344c57f..548a9d47c5c 100644 --- a/test/utils/structs/Checkpoints.t.sol +++ b/test/utils/structs/Checkpoints.t.sol @@ -30,7 +30,7 @@ contract CheckpointsTrace224Test is Test { } } - function _assertLatestCheckpoint(bool exist, uint32 key, uint224 value) internal { + function _assertLatestCheckpoint(bool exist, uint32 key, uint224 value) internal view { (bool _exist, uint32 _key, uint224 _value) = _ckpts.latestCheckpoint(); assertEq(_exist, exist); assertEq(_key, key); @@ -138,7 +138,7 @@ contract CheckpointsTrace208Test is Test { } } - function _assertLatestCheckpoint(bool exist, uint48 key, uint208 value) internal { + function _assertLatestCheckpoint(bool exist, uint48 key, uint208 value) internal view { (bool _exist, uint48 _key, uint208 _value) = _ckpts.latestCheckpoint(); assertEq(_exist, exist); assertEq(_key, key); @@ -246,7 +246,7 @@ contract CheckpointsTrace160Test is Test { } } - function _assertLatestCheckpoint(bool exist, uint96 key, uint160 value) internal { + function _assertLatestCheckpoint(bool exist, uint96 key, uint160 value) internal view { (bool _exist, uint96 _key, uint160 _value) = _ckpts.latestCheckpoint(); assertEq(_exist, exist); assertEq(_key, key); diff --git a/test/utils/structs/Heap.t.sol b/test/utils/structs/Heap.t.sol index 434f37f669a..ba7d7701209 100644 --- a/test/utils/structs/Heap.t.sol +++ b/test/utils/structs/Heap.t.sol @@ -12,7 +12,7 @@ contract Uint256HeapTest is Test { Heap.Uint256Heap internal heap; - function _validateHeap(function(uint256, uint256) view returns (bool) comp) internal { + function _validateHeap(function(uint256, uint256) view returns (bool) comp) internal view { for (uint32 i = 1; i < heap.length(); ++i) { assertFalse(comp(heap.tree[i], heap.tree[(i - 1) / 2])); } From 3da7a869aad95a3390e173f59558181329b0871f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 29 Oct 2024 10:25:02 +0100 Subject: [PATCH 46/84] Update dependency p-limit to v6 (#5104) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Hadrien Croubois Co-authored-by: cairo --- certora/run.js | 44 +++++++++++-------- package-lock.json | 109 +++++++++++++++++++++++++++++++++++++++++----- package.json | 2 +- 3 files changed, 126 insertions(+), 29 deletions(-) diff --git a/certora/run.js b/certora/run.js index 7b65534ea1d..a5ad9eece3b 100755 --- a/certora/run.js +++ b/certora/run.js @@ -7,11 +7,16 @@ // node certora/run.js AccessControl // node certora/run.js AccessControlHarness:AccessControl -const proc = require('child_process'); -const { PassThrough } = require('stream'); -const events = require('events'); - -const argv = require('yargs') +import { spawn } from 'child_process'; +import { PassThrough } from 'stream'; +import { once } from 'events'; +import path from 'path'; +import yargs from 'yargs'; +import { hideBin } from 'yargs/helpers'; +import pLimit from 'p-limit'; +import fs from 'fs/promises'; + +const argv = yargs(hideBin(process.argv)) .env('') .options({ all: { @@ -21,7 +26,7 @@ const argv = require('yargs') spec: { alias: 's', type: 'string', - default: __dirname + '/specs.json', + default: path.resolve(import.meta.dirname, 'specs.json'), }, parallel: { alias: 'p', @@ -38,18 +43,20 @@ const argv = require('yargs') type: 'array', default: [], }, - }).argv; + }) + .parse(); function match(entry, request) { const [reqSpec, reqContract] = request.split(':').reverse(); return entry.spec == reqSpec && (!reqContract || entry.contract == reqContract); } -const specs = require(argv.spec).filter(s => argv.all || argv._.some(r => match(s, r))); -const limit = require('p-limit')(argv.parallel); +const specs = JSON.parse(fs.readFileSync(argv.spec, 'utf8')).filter(s => argv.all || argv._.some(r => match(s, r))); + +const limit = pLimit(argv.parallel); if (argv._.length == 0 && !argv.all) { - console.error(`Warning: No specs requested. Did you forgot to toggle '--all'?`); + console.error(`Warning: No specs requested. Did you forget to toggle '--all'?`); } for (const r of argv._) { @@ -64,12 +71,13 @@ if (process.exitCode) { } for (const { spec, contract, files, options = [] } of specs) { - limit( - runCertora, - spec, - contract, - files, - [...options, ...argv.options].flatMap(opt => opt.split(' ')), + limit(() => + runCertora( + spec, + contract, + files, + [...options, ...argv.options].flatMap(opt => opt.split(' ')), + ), ); } @@ -79,7 +87,7 @@ async function runCertora(spec, contract, files, options = []) { if (argv.verbose) { console.log('Running:', args.join(' ')); } - const child = proc.spawn('certoraRun', args); + const child = spawn('certoraRun', args); const stream = new PassThrough(); const output = collect(stream); @@ -103,7 +111,7 @@ async function runCertora(spec, contract, files, options = []) { }); // wait for process end - const [code, signal] = await events.once(child, 'exit'); + const [code, signal] = await once(child, 'exit'); // error if (code || signal) { diff --git a/package-lock.json b/package-lock.json index 6066b617fda..bd5171b13f4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,7 +34,7 @@ "hardhat-ignore-warnings": "^0.2.11", "lodash.startcase": "^4.4.0", "micromatch": "^4.0.2", - "p-limit": "^3.1.0", + "p-limit": "^6.0.0", "prettier": "^3.0.0", "prettier-plugin-solidity": "^1.1.0", "rimraf": "^6.0.0", @@ -4618,6 +4618,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/eslint/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/eslint/node_modules/p-locate": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", @@ -4647,6 +4663,19 @@ "node": ">=8" } }, + "node_modules/eslint/node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/espree": { "version": "10.2.0", "resolved": "https://registry.npmjs.org/espree/-/espree-10.2.0.tgz", @@ -7683,6 +7712,22 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, + "node_modules/mocha/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/mocha/node_modules/p-locate": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", @@ -7754,6 +7799,19 @@ "node": ">=10" } }, + "node_modules/mocha/node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -8045,15 +8103,16 @@ } }, "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-6.0.0.tgz", + "integrity": "sha512-Dx+NzOuILWwjJE9OYtEKuQRy0i3c5QVAmDsVrvvRSgyNnPuB27D2DyEjl6QTNyeePaAHjaPk+ya0yA0Frld8RA==", "dev": true, + "license": "MIT", "dependencies": { - "yocto-queue": "^0.1.0" + "yocto-queue": "^1.1.1" }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -8337,6 +8396,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/preferred-pm/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/preferred-pm/node_modules/p-locate": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", @@ -8352,6 +8427,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/preferred-pm/node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -11399,12 +11487,13 @@ } }, "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.1.1.tgz", + "integrity": "sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==", "dev": true, + "license": "MIT", "engines": { - "node": ">=10" + "node": ">=12.20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" diff --git a/package.json b/package.json index 19c54c92a05..44c697ac7ef 100644 --- a/package.json +++ b/package.json @@ -76,7 +76,7 @@ "hardhat-ignore-warnings": "^0.2.11", "lodash.startcase": "^4.4.0", "micromatch": "^4.0.2", - "p-limit": "^3.1.0", + "p-limit": "^6.0.0", "prettier": "^3.0.0", "prettier-plugin-solidity": "^1.1.0", "rimraf": "^6.0.0", From 448efeea6640bbbc09373f03fbc9c88e280147ba Mon Sep 17 00:00:00 2001 From: Lohann Paterno Coutinho Ferreira Date: Tue, 29 Oct 2024 16:09:20 +0000 Subject: [PATCH 47/84] Optimize `log2` with a lookup table (#5236) Co-authored-by: Hadrien Croubois Co-authored-by: cairo --- contracts/utils/math/Math.sol | 72 ++++++++++++++++++----------------- 1 file changed, 38 insertions(+), 34 deletions(-) diff --git a/contracts/utils/math/Math.sol b/contracts/utils/math/Math.sol index 85a420b1a7b..cd55ff13085 100644 --- a/contracts/utils/math/Math.sol +++ b/contracts/utils/math/Math.sol @@ -537,41 +537,45 @@ library Math { * @dev Return the log in base 2 of a positive value rounded towards zero. * Returns 0 if given 0. */ - function log2(uint256 value) internal pure returns (uint256) { - uint256 result = 0; - uint256 exp; - unchecked { - exp = 128 * SafeCast.toUint(value > (1 << 128) - 1); - value >>= exp; - result += exp; - - exp = 64 * SafeCast.toUint(value > (1 << 64) - 1); - value >>= exp; - result += exp; - - exp = 32 * SafeCast.toUint(value > (1 << 32) - 1); - value >>= exp; - result += exp; - - exp = 16 * SafeCast.toUint(value > (1 << 16) - 1); - value >>= exp; - result += exp; - - exp = 8 * SafeCast.toUint(value > (1 << 8) - 1); - value >>= exp; - result += exp; - - exp = 4 * SafeCast.toUint(value > (1 << 4) - 1); - value >>= exp; - result += exp; - - exp = 2 * SafeCast.toUint(value > (1 << 2) - 1); - value >>= exp; - result += exp; - - result += SafeCast.toUint(value > 1); + function log2(uint256 x) internal pure returns (uint256 r) { + // If value has upper 128 bits set, log2 result is at least 128 + r = SafeCast.toUint(x > 0xffffffffffffffffffffffffffffffff) << 7; + // If upper 64 bits of 128-bit half set, add 64 to result + r |= SafeCast.toUint((x >> r) > 0xffffffffffffffff) << 6; + // If upper 32 bits of 64-bit half set, add 32 to result + r |= SafeCast.toUint((x >> r) > 0xffffffff) << 5; + // If upper 16 bits of 32-bit half set, add 16 to result + r |= SafeCast.toUint((x >> r) > 0xffff) << 4; + // If upper 8 bits of 16-bit half set, add 8 to result + r |= SafeCast.toUint((x >> r) > 0xff) << 3; + // If upper 4 bits of 8-bit half set, add 4 to result + r |= SafeCast.toUint((x >> r) > 0xf) << 2; + + // Shifts value right by the current result and use it as an index into this lookup table: + // + // | x (4 bits) | index | table[index] = MSB position | + // |------------|---------|-----------------------------| + // | 0000 | 0 | table[0] = 0 | + // | 0001 | 1 | table[1] = 0 | + // | 0010 | 2 | table[2] = 1 | + // | 0011 | 3 | table[3] = 1 | + // | 0100 | 4 | table[4] = 2 | + // | 0101 | 5 | table[5] = 2 | + // | 0110 | 6 | table[6] = 2 | + // | 0111 | 7 | table[7] = 2 | + // | 1000 | 8 | table[8] = 3 | + // | 1001 | 9 | table[9] = 3 | + // | 1010 | 10 | table[10] = 3 | + // | 1011 | 11 | table[11] = 3 | + // | 1100 | 12 | table[12] = 3 | + // | 1101 | 13 | table[13] = 3 | + // | 1110 | 14 | table[14] = 3 | + // | 1111 | 15 | table[15] = 3 | + // + // The lookup table is represented as a 32-byte value with the MSB positions for 0-15 in the last 16 bytes. + assembly ("memory-safe") { + r := or(r, byte(shr(r, x), 0x0000010102020202030303030303030300000000000000000000000000000000)) } - return result; } /** From dac63c4612df765c9d0f24d9148e912b8cd5032f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Mon, 18 Nov 2024 15:40:14 +0700 Subject: [PATCH 48/84] Fix docs in the extending-contracts section (#5300) --- ...rolUnrevokableAdmin.sol => AccessControlNonRevokableAdmin.sol} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename contracts/mocks/docs/access-control/{AccessControlUnrevokableAdmin.sol => AccessControlNonRevokableAdmin.sol} (100%) diff --git a/contracts/mocks/docs/access-control/AccessControlUnrevokableAdmin.sol b/contracts/mocks/docs/access-control/AccessControlNonRevokableAdmin.sol similarity index 100% rename from contracts/mocks/docs/access-control/AccessControlUnrevokableAdmin.sol rename to contracts/mocks/docs/access-control/AccessControlNonRevokableAdmin.sol From ffca4122992719614c64368861269cc6a87d16f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Wed, 20 Nov 2024 09:59:49 +0700 Subject: [PATCH 49/84] Note native ERC20 behavior in VestingWallet (#5299) Co-authored-by: Arr00 <13561405+arr00@users.noreply.github.com> --- contracts/finance/VestingWallet.sol | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/contracts/finance/VestingWallet.sol b/contracts/finance/VestingWallet.sol index 0e0321d0da2..b03c4968430 100644 --- a/contracts/finance/VestingWallet.sol +++ b/contracts/finance/VestingWallet.sol @@ -26,6 +26,11 @@ import {Ownable} from "../access/Ownable.sol"; * * NOTE: When using this contract with any token whose balance is adjusted automatically (i.e. a rebase token), make * sure to account the supply/balance adjustment in the vesting schedule to ensure the vested amount is as intended. + * + * NOTE: Chains with support for native ERC20s may allow the vesting wallet to withdraw the underlying asset as both an + * ERC20 and as native currency. For example, if chain C supports token A and the wallet gets deposited 100 A, then + * at 50% of the vesting period, the beneficiary can withdraw 50 A as ERC20 and 25 A as native currency (totaling 75 A). + * Consider disabling one of the withdrawal methods. */ contract VestingWallet is Context, Ownable { event EtherReleased(uint256 amount); From ccb5f2d8ca9d194623cf3cff7f010ab92715826b Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Wed, 20 Nov 2024 09:21:49 +0100 Subject: [PATCH 50/84] Fix 5.2 audit L-05, N-03, N-04, N-05 and N-06 issues (#5308) --- contracts/account/utils/draft-ERC4337Utils.sol | 11 +++-------- contracts/account/utils/draft-ERC7579Utils.sol | 10 +++++----- .../extensions/GovernorCountingOverridable.sol | 2 +- contracts/governance/utils/VotesExtended.sol | 2 +- contracts/interfaces/draft-IERC4337.sol | 2 +- contracts/proxy/Clones.sol | 4 ++-- contracts/utils/Bytes.sol | 12 +++++------- contracts/utils/CAIP10.sol | 4 +--- contracts/utils/CAIP2.sol | 2 -- contracts/utils/NoncesKeyed.sol | 4 ++-- test/account/utils/draft-ERC4337Utils.test.js | 7 ------- 11 files changed, 21 insertions(+), 39 deletions(-) diff --git a/contracts/account/utils/draft-ERC4337Utils.sol b/contracts/account/utils/draft-ERC4337Utils.sol index bf559b86d6e..7f72d3404e7 100644 --- a/contracts/account/utils/draft-ERC4337Utils.sol +++ b/contracts/account/utils/draft-ERC4337Utils.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.20; -import {IEntryPoint, PackedUserOperation} from "../../interfaces/draft-IERC4337.sol"; +import {PackedUserOperation} from "../../interfaces/draft-IERC4337.sol"; import {Math} from "../../utils/math/Math.sol"; import {Packing} from "../../utils/Packing.sol"; @@ -71,12 +71,7 @@ library ERC4337Utils { return (aggregator_, block.timestamp < validAfter || validUntil < block.timestamp); } - /// @dev Computes the hash of a user operation with the current entrypoint and chainid. - function hash(PackedUserOperation calldata self) internal view returns (bytes32) { - return hash(self, address(this), block.chainid); - } - - /// @dev Sames as {hash}, but with a custom entrypoint and chainid. + /// @dev Computes the hash of a user operation for a given entrypoint and chainid. function hash( PackedUserOperation calldata self, address entrypoint, @@ -129,7 +124,7 @@ library ERC4337Utils { // Following values are "per gas" uint256 maxPriorityFee = maxPriorityFeePerGas(self); uint256 maxFee = maxFeePerGas(self); - return Math.ternary(maxFee == maxPriorityFee, maxFee, Math.min(maxFee, maxPriorityFee + block.basefee)); + return Math.min(maxFee, maxPriorityFee + block.basefee); } } diff --git a/contracts/account/utils/draft-ERC7579Utils.sol b/contracts/account/utils/draft-ERC7579Utils.sol index de6bb6509ec..55fb7a09a8f 100644 --- a/contracts/account/utils/draft-ERC7579Utils.sol +++ b/contracts/account/utils/draft-ERC7579Utils.sol @@ -22,19 +22,19 @@ library ERC7579Utils { using Packing for *; /// @dev A single `call` execution. - CallType constant CALLTYPE_SINGLE = CallType.wrap(0x00); + CallType internal constant CALLTYPE_SINGLE = CallType.wrap(0x00); /// @dev A batch of `call` executions. - CallType constant CALLTYPE_BATCH = CallType.wrap(0x01); + CallType internal constant CALLTYPE_BATCH = CallType.wrap(0x01); /// @dev A `delegatecall` execution. - CallType constant CALLTYPE_DELEGATECALL = CallType.wrap(0xFF); + CallType internal constant CALLTYPE_DELEGATECALL = CallType.wrap(0xFF); /// @dev Default execution type that reverts on failure. - ExecType constant EXECTYPE_DEFAULT = ExecType.wrap(0x00); + ExecType internal constant EXECTYPE_DEFAULT = ExecType.wrap(0x00); /// @dev Execution type that does not revert on failure. - ExecType constant EXECTYPE_TRY = ExecType.wrap(0x01); + ExecType internal constant EXECTYPE_TRY = ExecType.wrap(0x01); /// @dev Emits when an {EXECTYPE_TRY} execution fails. event ERC7579TryExecuteFail(uint256 batchExecutionIndex, bytes result); diff --git a/contracts/governance/extensions/GovernorCountingOverridable.sol b/contracts/governance/extensions/GovernorCountingOverridable.sol index 9b46903e35d..6e20d9fbfdf 100644 --- a/contracts/governance/extensions/GovernorCountingOverridable.sol +++ b/contracts/governance/extensions/GovernorCountingOverridable.sol @@ -9,7 +9,7 @@ import {GovernorVotes} from "./GovernorVotes.sol"; /** * @dev Extension of {Governor} which enables delegatees to override the vote of their delegates. This module requires a - * token token that inherits `VotesExtended`. + * token that inherits {VotesExtended}. */ abstract contract GovernorCountingOverridable is GovernorVotes { bytes32 public constant OVERRIDE_BALLOT_TYPEHASH = diff --git a/contracts/governance/utils/VotesExtended.sol b/contracts/governance/utils/VotesExtended.sol index 62ddd5f7a2e..1850d1b98a6 100644 --- a/contracts/governance/utils/VotesExtended.sol +++ b/contracts/governance/utils/VotesExtended.sol @@ -6,7 +6,7 @@ import {Votes} from "./Votes.sol"; import {SafeCast} from "../../utils/math/SafeCast.sol"; /** - * @dev Extension of {Votes} that adds exposes checkpoints for delegations and balances. + * @dev Extension of {Votes} that adds checkpoints for delegations and balances. */ abstract contract VotesExtended is Votes { using SafeCast for uint256; diff --git a/contracts/interfaces/draft-IERC4337.sol b/contracts/interfaces/draft-IERC4337.sol index 9b1af56b6e5..603bce15b67 100644 --- a/contracts/interfaces/draft-IERC4337.sol +++ b/contracts/interfaces/draft-IERC4337.sol @@ -11,7 +11,7 @@ pragma solidity ^0.8.20; * - `callData` (`bytes`): The data to pass to the sender during the main execution call * - `callGasLimit` (`uint256`): The amount of gas to allocate the main execution call * - `verificationGasLimit` (`uint256`): The amount of gas to allocate for the verification step - * - `preVerificationGas` (`uint256`): Extra gas to pay the bunder + * - `preVerificationGas` (`uint256`): Extra gas to pay the bundler * - `maxFeePerGas` (`uint256`): Maximum fee per gas (similar to EIP-1559 max_fee_per_gas) * - `maxPriorityFeePerGas` (`uint256`): Maximum priority fee per gas (similar to EIP-1559 max_priority_fee_per_gas) * - `paymaster` (`address`): Address of paymaster contract, (or empty, if account pays for itself) diff --git a/contracts/proxy/Clones.sol b/contracts/proxy/Clones.sol index 99e25ab46db..19fe6c9f8ce 100644 --- a/contracts/proxy/Clones.sol +++ b/contracts/proxy/Clones.sol @@ -57,7 +57,7 @@ library Clones { * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`. * * This function uses the create2 opcode and a `salt` to deterministically deploy - * the clone. Using the same `implementation` and `salt` multiple time will revert, since + * the clone. Using the same `implementation` and `salt` multiple times will revert, since * the clones cannot be deployed twice at the same address. */ function cloneDeterministic(address implementation, bytes32 salt) internal returns (address instance) { @@ -163,7 +163,7 @@ library Clones { * access the arguments within the implementation, use {fetchCloneArgs}. * * This function uses the create2 opcode and a `salt` to deterministically deploy the clone. Using the same - * `implementation` and `salt` multiple time will revert, since the clones cannot be deployed twice at the same + * `implementation` and `salt` multiple times will revert, since the clones cannot be deployed twice at the same * address. */ function cloneDeterministicWithImmutableArgs( diff --git a/contracts/utils/Bytes.sol b/contracts/utils/Bytes.sol index 6fe49fd8d14..cf0cb8fcd7f 100644 --- a/contracts/utils/Bytes.sol +++ b/contracts/utils/Bytes.sol @@ -27,15 +27,13 @@ library Bytes { * NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf[Javascript's `Array.indexOf`] */ function indexOf(bytes memory buffer, bytes1 s, uint256 pos) internal pure returns (uint256) { - unchecked { - uint256 length = buffer.length; - for (uint256 i = pos; i < length; ++i) { - if (bytes1(_unsafeReadBytesOffset(buffer, i)) == s) { - return i; - } + uint256 length = buffer.length; + for (uint256 i = pos; i < length; ++i) { + if (bytes1(_unsafeReadBytesOffset(buffer, i)) == s) { + return i; } - return type(uint256).max; } + return type(uint256).max; } /** diff --git a/contracts/utils/CAIP10.sol b/contracts/utils/CAIP10.sol index 95aa2a97737..bd9ddbc9f17 100644 --- a/contracts/utils/CAIP10.sol +++ b/contracts/utils/CAIP10.sol @@ -2,10 +2,9 @@ pragma solidity ^0.8.24; -import {SafeCast} from "./math/SafeCast.sol"; import {Bytes} from "./Bytes.sol"; -import {CAIP2} from "./CAIP2.sol"; import {Strings} from "./Strings.sol"; +import {CAIP2} from "./CAIP2.sol"; /** * @dev Helper library to format and parse CAIP-10 identifiers @@ -16,7 +15,6 @@ import {Strings} from "./Strings.sol"; * account_address: [-.%a-zA-Z0-9]{1,128} */ library CAIP10 { - using SafeCast for uint256; using Strings for address; using Bytes for bytes; diff --git a/contracts/utils/CAIP2.sol b/contracts/utils/CAIP2.sol index cfad67e00d0..c6789ce4384 100644 --- a/contracts/utils/CAIP2.sol +++ b/contracts/utils/CAIP2.sol @@ -2,7 +2,6 @@ pragma solidity ^0.8.24; -import {SafeCast} from "./math/SafeCast.sol"; import {Bytes} from "./Bytes.sol"; import {Strings} from "./Strings.sol"; @@ -15,7 +14,6 @@ import {Strings} from "./Strings.sol"; * reference: [-_a-zA-Z0-9]{1,32} */ library CAIP2 { - using SafeCast for uint256; using Strings for uint256; using Bytes for bytes; diff --git a/contracts/utils/NoncesKeyed.sol b/contracts/utils/NoncesKeyed.sol index 133b0c3fe4a..c775700d27b 100644 --- a/contracts/utils/NoncesKeyed.sol +++ b/contracts/utils/NoncesKeyed.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.20; import {Nonces} from "./Nonces.sol"; /** - * @dev Alternative to {Nonces}, that support key-ed nonces. + * @dev Alternative to {Nonces}, that supports key-ed nonces. * * Follows the https://eips.ethereum.org/EIPS/eip-4337#semi-abstracted-nonce-support[ERC-4337's semi-abstracted nonce system]. */ @@ -19,7 +19,7 @@ abstract contract NoncesKeyed is Nonces { /** * @dev Consumes the next unused nonce for an address and key. * - * Returns the current value without the key prefix. Consumed nonce is increased, so calling this functions twice + * Returns the current value without the key prefix. Consumed nonce is increased, so calling this function twice * with the same arguments will return different (sequential) results. */ function _useNonce(address owner, uint192 key) internal virtual returns (uint256) { diff --git a/test/account/utils/draft-ERC4337Utils.test.js b/test/account/utils/draft-ERC4337Utils.test.js index 8374bc745c9..5138a5d8ee8 100644 --- a/test/account/utils/draft-ERC4337Utils.test.js +++ b/test/account/utils/draft-ERC4337Utils.test.js @@ -133,13 +133,6 @@ describe('ERC4337Utils', function () { }); describe('hash', function () { - it('returns the user operation hash', async function () { - const userOp = new UserOperation({ sender: this.sender, nonce: 1 }); - const chainId = await ethers.provider.getNetwork().then(({ chainId }) => chainId); - - expect(this.utils.$hash(userOp.packed)).to.eventually.equal(userOp.hash(this.utils.target, chainId)); - }); - it('returns the operation hash with specified entrypoint and chainId', async function () { const userOp = new UserOperation({ sender: this.sender, nonce: 1 }); const chainId = 0xdeadbeef; From a277d472d657dbbcd88a3de5cae0c806130e2df2 Mon Sep 17 00:00:00 2001 From: wizard <112275929+famouswizard@users.noreply.github.com> Date: Thu, 21 Nov 2024 17:20:14 +0300 Subject: [PATCH 51/84] Fix typo in GUIDELINES.md (#5297) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernesto García --- GUIDELINES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GUIDELINES.md b/GUIDELINES.md index a1418d6528a..2c21e956b1e 100644 --- a/GUIDELINES.md +++ b/GUIDELINES.md @@ -55,7 +55,7 @@ External contributions must be reviewed separately by multiple maintainers. Automation should be used as much as possible to reduce the possibility of human error and forgetfulness. -Automations that make use of sensitive credentials must use secure secret management, and must be strengthened against attacks such as [those on GitHub Actions worklows](https://github.com/nikitastupin/pwnhub). +Automations that make use of sensitive credentials must use secure secret management, and must be strengthened against attacks such as [those on GitHub Actions workflows](https://github.com/nikitastupin/pwnhub). Some other examples of automation are: From 7105693e3c72ad73e4d722cc3db79d065b9a0875 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 21 Nov 2024 16:34:00 +0100 Subject: [PATCH 52/84] Change NoncesKeyed._useNonce to return a keyed value (#5312) --- contracts/utils/NoncesKeyed.sol | 31 +++++++++++++-------- test/utils/Nonces.behavior.js | 49 +++++++++++++++++++++++++++++---- 2 files changed, 63 insertions(+), 17 deletions(-) diff --git a/contracts/utils/NoncesKeyed.sol b/contracts/utils/NoncesKeyed.sol index c775700d27b..7984dc70e4e 100644 --- a/contracts/utils/NoncesKeyed.sol +++ b/contracts/utils/NoncesKeyed.sol @@ -13,7 +13,7 @@ abstract contract NoncesKeyed is Nonces { /// @dev Returns the next unused nonce for an address and key. Result contains the key prefix. function nonces(address owner, uint192 key) public view virtual returns (uint256) { - return key == 0 ? nonces(owner) : ((uint256(key) << 64) | _nonces[owner][key]); + return key == 0 ? nonces(owner) : _pack(key, _nonces[owner][key]); } /** @@ -27,7 +27,7 @@ abstract contract NoncesKeyed is Nonces { // decremented or reset. This guarantees that the nonce never overflows. unchecked { // It is important to do x++ and not ++x here. - return key == 0 ? _useNonce(owner) : _nonces[owner][key]++; + return key == 0 ? _useNonce(owner) : _pack(key, _nonces[owner][key]++); } } @@ -39,7 +39,13 @@ abstract contract NoncesKeyed is Nonces { * - use the last 24 bytes for the nonce */ function _useCheckedNonce(address owner, uint256 keyNonce) internal virtual override { - _useCheckedNonce(owner, uint192(keyNonce >> 64), uint64(keyNonce)); + (uint192 key, ) = _unpack(keyNonce); + if (key == 0) { + super._useCheckedNonce(owner, keyNonce); + } else { + uint256 current = _useNonce(owner, key); + if (keyNonce != current) revert InvalidAccountNonce(owner, current); + } } /** @@ -48,13 +54,16 @@ abstract contract NoncesKeyed is Nonces { * This version takes the key and the nonce as two different parameters. */ function _useCheckedNonce(address owner, uint192 key, uint64 nonce) internal virtual { - if (key == 0) { - super._useCheckedNonce(owner, nonce); - } else { - uint256 current = _useNonce(owner, key); - if (nonce != current) { - revert InvalidAccountNonce(owner, current); - } - } + _useCheckedNonce(owner, _pack(key, nonce)); + } + + /// @dev Pack key and nonce into a keyNonce + function _pack(uint192 key, uint64 nonce) private pure returns (uint256) { + return (uint256(key) << 64) | nonce; + } + + /// @dev Unpack a keyNonce into its key and nonce components + function _unpack(uint256 keyNonce) private pure returns (uint192 key, uint64 nonce) { + return (uint192(keyNonce >> 64), uint64(keyNonce)); } } diff --git a/test/utils/Nonces.behavior.js b/test/utils/Nonces.behavior.js index 17073966427..32201f690d6 100644 --- a/test/utils/Nonces.behavior.js +++ b/test/utils/Nonces.behavior.js @@ -86,7 +86,7 @@ function shouldBehaveLikeNoncesKeyed() { await expect(this.mock.$_useNonce(sender, ethers.Typed.uint192(0n))) .to.emit(this.mock, 'return$_useNonce_address_uint192') - .withArgs(1n); + .withArgs(keyOffset(0n) + 1n); expect(this.mock.nonces(sender, ethers.Typed.uint192(0n))).to.eventually.equal(keyOffset(0n) + 2n); expect(this.mock.nonces(sender, ethers.Typed.uint192(17n))).to.eventually.equal(keyOffset(17n) + 0n); @@ -98,18 +98,18 @@ function shouldBehaveLikeNoncesKeyed() { await expect(this.mock.$_useNonce(sender, ethers.Typed.uint192(17n))) .to.emit(this.mock, 'return$_useNonce_address_uint192') - .withArgs(0n); + .withArgs(keyOffset(17n) + 0n); await expect(this.mock.$_useNonce(sender, ethers.Typed.uint192(17n))) .to.emit(this.mock, 'return$_useNonce_address_uint192') - .withArgs(1n); + .withArgs(keyOffset(17n) + 1n); expect(this.mock.nonces(sender, ethers.Typed.uint192(0n))).to.eventually.equal(keyOffset(0n) + 0n); expect(this.mock.nonces(sender, ethers.Typed.uint192(17n))).to.eventually.equal(keyOffset(17n) + 2n); }); }); - describe('_useCheckedNonce', function () { + describe('_useCheckedNonce(address, uint256)', function () { it('default variant uses key 0', async function () { const currentNonce = await this.mock.nonces(sender, ethers.Typed.uint192(0n)); @@ -135,12 +135,49 @@ function shouldBehaveLikeNoncesKeyed() { // reuse same nonce await expect(this.mock.$_useCheckedNonce(sender, currentNonce)) .to.be.revertedWithCustomError(this.mock, 'InvalidAccountNonce') - .withArgs(sender, 1); + .withArgs(sender, currentNonce + 1n); // use "future" nonce too early await expect(this.mock.$_useCheckedNonce(sender, currentNonce + 10n)) .to.be.revertedWithCustomError(this.mock, 'InvalidAccountNonce') - .withArgs(sender, 1); + .withArgs(sender, currentNonce + 1n); + }); + }); + + describe('_useCheckedNonce(address, uint192, uint64)', function () { + const MASK = 0xffffffffffffffffn; + + it('default variant uses key 0', async function () { + const currentNonce = await this.mock.nonces(sender, ethers.Typed.uint192(0n)); + + await this.mock.$_useCheckedNonce(sender, ethers.Typed.uint192(0n), currentNonce); + + expect(this.mock.nonces(sender, ethers.Typed.uint192(0n))).to.eventually.equal(currentNonce + 1n); + }); + + it('use nonce at another key', async function () { + const currentNonce = await this.mock.nonces(sender, ethers.Typed.uint192(17n)); + + await this.mock.$_useCheckedNonce(sender, ethers.Typed.uint192(17n), currentNonce & MASK); + + expect(this.mock.nonces(sender, ethers.Typed.uint192(17n))).to.eventually.equal(currentNonce + 1n); + }); + + it('reverts when nonce is not the expected', async function () { + const currentNonce = await this.mock.nonces(sender, ethers.Typed.uint192(42n)); + + // use and increment + await this.mock.$_useCheckedNonce(sender, ethers.Typed.uint192(42n), currentNonce & MASK); + + // reuse same nonce + await expect(this.mock.$_useCheckedNonce(sender, ethers.Typed.uint192(42n), currentNonce & MASK)) + .to.be.revertedWithCustomError(this.mock, 'InvalidAccountNonce') + .withArgs(sender, currentNonce + 1n); + + // use "future" nonce too early + await expect(this.mock.$_useCheckedNonce(sender, ethers.Typed.uint192(42n), (currentNonce & MASK) + 10n)) + .to.be.revertedWithCustomError(this.mock, 'InvalidAccountNonce') + .withArgs(sender, currentNonce + 1n); }); }); }); From ccb39d27656fc36eea331e3403c52b057885634d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Thu, 21 Nov 2024 22:43:13 +0700 Subject: [PATCH 53/84] Improve general documentation for 5.2 (#5310) --- contracts/governance/utils/VotesExtended.sol | 2 +- contracts/interfaces/draft-IERC4337.sol | 6 +++--- contracts/proxy/Clones.sol | 4 ++-- contracts/utils/NoncesKeyed.sol | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/contracts/governance/utils/VotesExtended.sol b/contracts/governance/utils/VotesExtended.sol index 1850d1b98a6..82e4624fb0d 100644 --- a/contracts/governance/utils/VotesExtended.sol +++ b/contracts/governance/utils/VotesExtended.sol @@ -13,7 +13,7 @@ abstract contract VotesExtended is Votes { using Checkpoints for Checkpoints.Trace160; using Checkpoints for Checkpoints.Trace208; - mapping(address delegatee => Checkpoints.Trace160) private _delegateCheckpoints; + mapping(address delegator => Checkpoints.Trace160) private _delegateCheckpoints; mapping(address account => Checkpoints.Trace208) private _balanceOfCheckpoints; /** diff --git a/contracts/interfaces/draft-IERC4337.sol b/contracts/interfaces/draft-IERC4337.sol index 603bce15b67..e98801ead39 100644 --- a/contracts/interfaces/draft-IERC4337.sol +++ b/contracts/interfaces/draft-IERC4337.sol @@ -27,7 +27,7 @@ pragma solidity ^0.8.20; * - `callData` (`bytes`) * - `accountGasLimits` (`bytes32`): concatenation of verificationGas (16 bytes) and callGas (16 bytes) * - `preVerificationGas` (`uint256`) - * - `gasFees` (`bytes32`): concatenation of maxPriorityFee (16 bytes) and maxFeePerGas (16 bytes) + * - `gasFees` (`bytes32`): concatenation of maxPriorityFeePerGas (16 bytes) and maxFeePerGas (16 bytes) * - `paymasterAndData` (`bytes`): concatenation of paymaster fields (or empty) * - `signature` (`bytes`) */ @@ -38,8 +38,8 @@ struct PackedUserOperation { bytes callData; bytes32 accountGasLimits; // `abi.encodePacked(verificationGasLimit, callGasLimit)` 16 bytes each uint256 preVerificationGas; - bytes32 gasFees; // `abi.encodePacked(maxPriorityFee, maxFeePerGas)` 16 bytes each - bytes paymasterAndData; // `abi.encodePacked(paymaster, paymasterVerificationGasLimit, paymasterPostOpGasLimit, paymasterData)` + bytes32 gasFees; // `abi.encodePacked(maxPriorityFeePerGas, maxFeePerGas)` 16 bytes each + bytes paymasterAndData; // `abi.encodePacked(paymaster, paymasterVerificationGasLimit, paymasterPostOpGasLimit, paymasterData)` (20 bytes, 16 bytes, 16 bytes, dynamic) bytes signature; } diff --git a/contracts/proxy/Clones.sol b/contracts/proxy/Clones.sol index 19fe6c9f8ce..6bef4b4845e 100644 --- a/contracts/proxy/Clones.sol +++ b/contracts/proxy/Clones.sol @@ -163,8 +163,8 @@ library Clones { * access the arguments within the implementation, use {fetchCloneArgs}. * * This function uses the create2 opcode and a `salt` to deterministically deploy the clone. Using the same - * `implementation` and `salt` multiple times will revert, since the clones cannot be deployed twice at the same - * address. + * `implementation`, `args` and `salt` multiple time will revert, since the clones cannot be deployed twice + * at the same address. */ function cloneDeterministicWithImmutableArgs( address implementation, diff --git a/contracts/utils/NoncesKeyed.sol b/contracts/utils/NoncesKeyed.sol index 7984dc70e4e..27e0685b3a3 100644 --- a/contracts/utils/NoncesKeyed.sol +++ b/contracts/utils/NoncesKeyed.sol @@ -35,8 +35,8 @@ abstract contract NoncesKeyed is Nonces { * @dev Same as {_useNonce} but checking that `nonce` is the next valid for `owner`. * * This version takes the key and the nonce in a single uint256 parameter: - * - use the first 8 bytes for the key - * - use the last 24 bytes for the nonce + * - use the first 24 bytes for the key + * - use the last 8 bytes for the nonce */ function _useCheckedNonce(address owner, uint256 keyNonce) internal virtual override { (uint192 key, ) = _unpack(keyNonce); From 23f4452b214efabb7240dcbdb82b66b9cc23faaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Fri, 22 Nov 2024 23:45:05 +0800 Subject: [PATCH 54/84] Document voting counting until casted in GovernorCountingOverridable (#5309) Co-authored-by: Hadrien Croubois --- .../extensions/GovernorCountingOverridable.sol | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/contracts/governance/extensions/GovernorCountingOverridable.sol b/contracts/governance/extensions/GovernorCountingOverridable.sol index 6e20d9fbfdf..2ab669fa00c 100644 --- a/contracts/governance/extensions/GovernorCountingOverridable.sol +++ b/contracts/governance/extensions/GovernorCountingOverridable.sol @@ -52,6 +52,11 @@ abstract contract GovernorCountingOverridable is GovernorVotes { /** * @dev See {IGovernor-hasVoted}. + * + * NOTE: Calling {castVote} (or similar) casts a vote using the voting power that is delegated to the voter. + * Conversely, calling {castOverrideVote} (or similar) uses the voting power of the account itself, from its asset + * balances. Casting an "override vote" does not count as voting and won't be reflected by this getter. Consider + * using {hasVotedOverride} to check if an account has casted an "override vote" for a given proposal id. */ function hasVoted(uint256 proposalId, address account) public view virtual override returns (bool) { return _proposalVotes[proposalId].voteReceipt[account].casted != 0; @@ -120,7 +125,11 @@ abstract contract GovernorCountingOverridable is GovernorVotes { return totalWeight; } - /// @dev Variant of {Governor-_countVote} that deals with vote overrides. + /** + * @dev Variant of {Governor-_countVote} that deals with vote overrides. + * + * NOTE: See {hasVoted} for more details about the difference between {castVote} and {castOverrideVote}. + */ function _countOverride(uint256 proposalId, address account, uint8 support) internal virtual returns (uint256) { ProposalVote storage proposalVote = _proposalVotes[proposalId]; @@ -150,7 +159,7 @@ abstract contract GovernorCountingOverridable is GovernorVotes { return overridenWeight; } - /// @dev variant of {Governor-_castVote} that deals with vote overrides. + /// @dev Variant of {Governor-_castVote} that deals with vote overrides. function _castOverride( uint256 proposalId, address account, From d11ed2fb0a0130363b1e82d5742ad7516df0e749 Mon Sep 17 00:00:00 2001 From: leopardracer <136604165+leopardracer@users.noreply.github.com> Date: Fri, 22 Nov 2024 17:48:56 +0200 Subject: [PATCH 55/84] fix: typos in documentation files (#5305) --- .changeset/proud-planes-arrive.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/proud-planes-arrive.md b/.changeset/proud-planes-arrive.md index 6408976414d..60c831bd690 100644 --- a/.changeset/proud-planes-arrive.md +++ b/.changeset/proud-planes-arrive.md @@ -2,4 +2,4 @@ 'openzeppelin-solidity': minor --- -`Bytes`: Add a library of common operation that operate on `bytes` objects. +`Bytes`: Add a library of common operations that operate on `bytes` objects. From b3ce884628a1c9adf543cf8b0f1685307a006cb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Mon, 25 Nov 2024 18:05:40 +0800 Subject: [PATCH 56/84] Refactor `parseUint`, `parseInt` and `parseHexUint` to check bounds (#5304) Co-authored-by: Hadrien Croubois --- contracts/utils/Strings.sol | 53 +++++++++++++++++++++++++++++++------ test/utils/Strings.t.sol | 23 ++++++++++++++++ test/utils/Strings.test.js | 15 +++++++++++ 3 files changed, 83 insertions(+), 8 deletions(-) diff --git a/contracts/utils/Strings.sol b/contracts/utils/Strings.sol index c353212927e..161a2b576d7 100644 --- a/contracts/utils/Strings.sol +++ b/contracts/utils/Strings.sol @@ -158,7 +158,7 @@ library Strings { * NOTE: This function will revert if the result does not fit in a `uint256`. */ function tryParseUint(string memory input) internal pure returns (bool success, uint256 value) { - return tryParseUint(input, 0, bytes(input).length); + return _tryParseUintUncheckedBounds(input, 0, bytes(input).length); } /** @@ -172,6 +172,18 @@ library Strings { uint256 begin, uint256 end ) internal pure returns (bool success, uint256 value) { + if (end > bytes(input).length || begin > end) return (false, 0); + return _tryParseUintUncheckedBounds(input, begin, end); + } + + /** + * @dev Variant of {tryParseUint} that does not check bounds and returns (true, 0) if they are invalid. + */ + function _tryParseUintUncheckedBounds( + string memory input, + uint256 begin, + uint256 end + ) private pure returns (bool success, uint256 value) { bytes memory buffer = bytes(input); uint256 result = 0; @@ -216,7 +228,7 @@ library Strings { * NOTE: This function will revert if the absolute value of the result does not fit in a `uint256`. */ function tryParseInt(string memory input) internal pure returns (bool success, int256 value) { - return tryParseInt(input, 0, bytes(input).length); + return _tryParseIntUncheckedBounds(input, 0, bytes(input).length); } uint256 private constant ABS_MIN_INT256 = 2 ** 255; @@ -232,10 +244,22 @@ library Strings { uint256 begin, uint256 end ) internal pure returns (bool success, int256 value) { + if (end > bytes(input).length || begin > end) return (false, 0); + return _tryParseIntUncheckedBounds(input, begin, end); + } + + /** + * @dev Variant of {tryParseInt} that does not check bounds and returns (true, 0) if they are invalid. + */ + function _tryParseIntUncheckedBounds( + string memory input, + uint256 begin, + uint256 end + ) private pure returns (bool success, int256 value) { bytes memory buffer = bytes(input); // Check presence of a negative sign. - bytes1 sign = bytes1(_unsafeReadBytesOffset(buffer, begin)); + bytes1 sign = begin == end ? bytes1(0) : bytes1(_unsafeReadBytesOffset(buffer, begin)); // don't do out-of-bound (possibly unsafe) read if sub-string is empty bool positiveSign = sign == bytes1("+"); bool negativeSign = sign == bytes1("-"); uint256 offset = (positiveSign || negativeSign).toUint(); @@ -280,7 +304,7 @@ library Strings { * NOTE: This function will revert if the result does not fit in a `uint256`. */ function tryParseHexUint(string memory input) internal pure returns (bool success, uint256 value) { - return tryParseHexUint(input, 0, bytes(input).length); + return _tryParseHexUintUncheckedBounds(input, 0, bytes(input).length); } /** @@ -294,10 +318,22 @@ library Strings { uint256 begin, uint256 end ) internal pure returns (bool success, uint256 value) { + if (end > bytes(input).length || begin > end) return (false, 0); + return _tryParseHexUintUncheckedBounds(input, begin, end); + } + + /** + * @dev Variant of {tryParseHexUint} that does not check bounds and returns (true, 0) if they are invalid. + */ + function _tryParseHexUintUncheckedBounds( + string memory input, + uint256 begin, + uint256 end + ) private pure returns (bool success, uint256 value) { bytes memory buffer = bytes(input); // skip 0x prefix if present - bool hasPrefix = bytes2(_unsafeReadBytesOffset(buffer, begin)) == bytes2("0x"); + bool hasPrefix = (begin < end + 1) && bytes2(_unsafeReadBytesOffset(buffer, begin)) == bytes2("0x"); // don't do out-of-bound (possibly unsafe) read if sub-string is empty uint256 offset = hasPrefix.toUint() * 2; uint256 result = 0; @@ -355,12 +391,13 @@ library Strings { uint256 end ) internal pure returns (bool success, address value) { // check that input is the correct length - bool hasPrefix = bytes2(_unsafeReadBytesOffset(bytes(input), begin)) == bytes2("0x"); + bool hasPrefix = (begin < end + 1) && bytes2(_unsafeReadBytesOffset(bytes(input), begin)) == bytes2("0x"); // don't do out-of-bound (possibly unsafe) read if sub-string is empty + uint256 expectedLength = 40 + hasPrefix.toUint() * 2; - if (end - begin == expectedLength) { + if (end - begin == expectedLength && end <= bytes(input).length) { // length guarantees that this does not overflow, and value is at most type(uint160).max - (bool s, uint256 v) = tryParseHexUint(input, begin, end); + (bool s, uint256 v) = _tryParseHexUintUncheckedBounds(input, begin, end); return (s, address(uint160(v))); } else { return (false, address(0)); diff --git a/test/utils/Strings.t.sol b/test/utils/Strings.t.sol index 45e11342916..fe3c90bd115 100644 --- a/test/utils/Strings.t.sol +++ b/test/utils/Strings.t.sol @@ -24,4 +24,27 @@ contract StringsTest is Test { function testParseChecksumHex(address value) external pure { assertEq(value, value.toChecksumHexString().parseAddress()); } + + function testTryParseHexUintExtendedEnd(string memory random) external pure { + uint256 length = bytes(random).length; + assembly ("memory-safe") { + mstore(add(add(random, 0x20), length), 0x3030303030303030303030303030303030303030303030303030303030303030) + } + + (bool success, ) = random.tryParseHexUint(1, length + 1); + assertFalse(success); + } + + function testTryParseAddressExtendedEnd(address random, uint256 begin) external pure { + begin = bound(begin, 3, 43); + string memory input = random.toHexString(); + uint256 length = bytes(input).length; + + assembly ("memory-safe") { + mstore(add(add(input, 0x20), length), 0x3030303030303030303030303030303030303030303030303030303030303030) + } + + (bool success, ) = input.tryParseAddress(begin, begin + 40); + assertFalse(success); + } } diff --git a/test/utils/Strings.test.js b/test/utils/Strings.test.js index 5a47d4d10de..0b2d87190d3 100644 --- a/test/utils/Strings.test.js +++ b/test/utils/Strings.test.js @@ -240,6 +240,11 @@ describe('Strings', function () { expect(await this.mock.$tryParseUint('1 000')).deep.equal([false, 0n]); }); + it('parseUint invalid range', async function () { + expect(this.mock.$parseUint('12', 3, 2)).to.be.revertedWithCustomError(this.mock, 'StringsInvalidChar'); + expect(await this.mock.$tryParseUint('12', 3, 2)).to.deep.equal([false, 0n]); + }); + it('parseInt overflow', async function () { await expect(this.mock.$parseInt((ethers.MaxUint256 + 1n).toString(10))).to.be.revertedWithPanic( PANIC_CODES.ARITHMETIC_OVERFLOW, @@ -276,6 +281,11 @@ describe('Strings', function () { expect(await this.mock.$tryParseInt('1 000')).to.deep.equal([false, 0n]); }); + it('parseInt invalid range', async function () { + expect(this.mock.$parseInt('-12', 3, 2)).to.be.revertedWithCustomError(this.mock, 'StringsInvalidChar'); + expect(await this.mock.$tryParseInt('-12', 3, 2)).to.deep.equal([false, 0n]); + }); + it('parseHexUint overflow', async function () { await expect(this.mock.$parseHexUint((ethers.MaxUint256 + 1n).toString(16))).to.be.revertedWithPanic( PANIC_CODES.ARITHMETIC_OVERFLOW, @@ -303,6 +313,11 @@ describe('Strings', function () { expect(await this.mock.$tryParseHexUint('1 000')).to.deep.equal([false, 0n]); }); + it('parseHexUint invalid begin and end', async function () { + expect(this.mock.$parseHexUint('0x', 3, 2)).to.be.revertedWithCustomError(this.mock, 'StringsInvalidChar'); + expect(await this.mock.$tryParseHexUint('0x', 3, 2)).to.deep.equal([false, 0n]); + }); + it('parseAddress invalid format', async function () { for (const addr of [ '0x736a507fB2881d6bB62dcA54673CF5295dC07833', // valid From ed98138a7820e7608becfb2c457533af6ba91c05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Mon, 25 Nov 2024 11:15:23 -0600 Subject: [PATCH 57/84] Add missing docstrings (#5311) Co-authored-by: cairo Co-authored-by: Hadrien Croubois --- .../GovernorCountingOverridable.sol | 5 +++- contracts/interfaces/draft-IERC7579.sol | 27 +++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/contracts/governance/extensions/GovernorCountingOverridable.sol b/contracts/governance/extensions/GovernorCountingOverridable.sol index 2ab669fa00c..c3930c131fc 100644 --- a/contracts/governance/extensions/GovernorCountingOverridable.sol +++ b/contracts/governance/extensions/GovernorCountingOverridable.sol @@ -35,7 +35,10 @@ abstract contract GovernorCountingOverridable is GovernorVotes { mapping(address voter => VoteReceipt) voteReceipt; } - event VoteReduced(address indexed voter, uint256 proposalId, uint8 support, uint256 weight); + /// @dev The vote was reduced by `weight` after an override over the `delegate` by the original token holder + event VoteReduced(address indexed delegate, uint256 proposalId, uint8 support, uint256 weight); + + /// @dev A delegated vote on `proposalId` was overridden by `weight` event OverrideVoteCast(address indexed voter, uint256 proposalId, uint8 support, uint256 weight, string reason); error GovernorAlreadyOverridenVote(address account); diff --git a/contracts/interfaces/draft-IERC7579.sol b/contracts/interfaces/draft-IERC7579.sol index 47f1627f682..ae30e976709 100644 --- a/contracts/interfaces/draft-IERC7579.sol +++ b/contracts/interfaces/draft-IERC7579.sol @@ -10,6 +10,7 @@ uint256 constant MODULE_TYPE_EXECUTOR = 2; uint256 constant MODULE_TYPE_FALLBACK = 3; uint256 constant MODULE_TYPE_HOOK = 4; +/// @dev Minimal configuration interface for ERC-7579 modules interface IERC7579Module { /** * @dev This function is called by the smart account during installation of the module @@ -36,6 +37,11 @@ interface IERC7579Module { function isModuleType(uint256 moduleTypeId) external view returns (bool); } +/** + * @dev ERC-7579 Validation module (type 1). + * + * A module that implements logic to validate user operations and signatures. + */ interface IERC7579Validator is IERC7579Module { /** * @dev Validates a UserOperation @@ -63,6 +69,12 @@ interface IERC7579Validator is IERC7579Module { ) external view returns (bytes4); } +/** + * @dev ERC-7579 Hooks module (type 4). + * + * A module that implements logic to execute before and after the account executes a user operation, + * either individually or batched. + */ interface IERC7579Hook is IERC7579Module { /** * @dev Called by the smart account before execution @@ -93,6 +105,11 @@ struct Execution { bytes callData; } +/** + * @dev ERC-7579 Execution. + * + * Accounts should implement this interface so that the Entrypoint and ERC-7579 modules can execute operations. + */ interface IERC7579Execution { /** * @dev Executes a transaction on behalf of the account. @@ -119,6 +136,11 @@ interface IERC7579Execution { ) external returns (bytes[] memory returnData); } +/** + * @dev ERC-7579 Account Config. + * + * Accounts should implement this interface to exposes information that identifies the account, supported modules and capabilities. + */ interface IERC7579AccountConfig { /** * @dev Returns the account id of the smart account @@ -148,6 +170,11 @@ interface IERC7579AccountConfig { function supportsModule(uint256 moduleTypeId) external view returns (bool); } +/** + * @dev ERC-7579 Module Config. + * + * Accounts should implement this interface to allows installing and uninstalling modules. + */ interface IERC7579ModuleConfig { event ModuleInstalled(uint256 moduleTypeId, address module); event ModuleUninstalled(uint256 moduleTypeId, address module); From 0513853ca54b35eed39bd787bdc80ca29073d774 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Mon, 25 Nov 2024 11:28:59 -0600 Subject: [PATCH 58/84] Rephrase `VoteReduced` event docs (#5318) --- contracts/governance/extensions/GovernorCountingOverridable.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/governance/extensions/GovernorCountingOverridable.sol b/contracts/governance/extensions/GovernorCountingOverridable.sol index c3930c131fc..e89fb49d5af 100644 --- a/contracts/governance/extensions/GovernorCountingOverridable.sol +++ b/contracts/governance/extensions/GovernorCountingOverridable.sol @@ -35,7 +35,7 @@ abstract contract GovernorCountingOverridable is GovernorVotes { mapping(address voter => VoteReceipt) voteReceipt; } - /// @dev The vote was reduced by `weight` after an override over the `delegate` by the original token holder + /// @dev The votes casted by `delegate` were reduced by `weight` after an override vote was casted by the original token holder event VoteReduced(address indexed delegate, uint256 proposalId, uint8 support, uint256 weight); /// @dev A delegated vote on `proposalId` was overridden by `weight` From e1d44e0342ab6d8d884d0c8c48cfc55ac10fb5ec Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Mon, 25 Nov 2024 18:35:09 +0100 Subject: [PATCH 59/84] Add `factory()`, `factoryData()` and `paymasterData()` helpers to ERC4337Utils (#5313) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernesto García --- .../account/utils/draft-ERC4337Utils.sol | 29 ++++++++- test/account/utils/draft-ERC4337Utils.test.js | 62 ++++++++++++++++--- test/account/utils/draft-ERC7579Utils.test.js | 5 +- test/helpers/erc4337.js | 32 +++++++--- 4 files changed, 104 insertions(+), 24 deletions(-) diff --git a/contracts/account/utils/draft-ERC4337Utils.sol b/contracts/account/utils/draft-ERC4337Utils.sol index 7f72d3404e7..2caa3bd2112 100644 --- a/contracts/account/utils/draft-ERC4337Utils.sol +++ b/contracts/account/utils/draft-ERC4337Utils.sol @@ -98,6 +98,16 @@ library ERC4337Utils { return result; } + /// @dev Returns `factory` from the {PackedUserOperation}, or address(0) if the initCode is empty or not properly formatted. + function factory(PackedUserOperation calldata self) internal pure returns (address) { + return self.initCode.length < 20 ? address(0) : address(bytes20(self.initCode[0:20])); + } + + /// @dev Returns `factoryData` from the {PackedUserOperation}, or empty bytes if the initCode is empty or not properly formatted. + function factoryData(PackedUserOperation calldata self) internal pure returns (bytes calldata) { + return self.initCode.length < 20 ? _emptyCalldataBytes() : self.initCode[20:]; + } + /// @dev Returns `verificationGasLimit` from the {PackedUserOperation}. function verificationGasLimit(PackedUserOperation calldata self) internal pure returns (uint256) { return uint128(self.accountGasLimits.extract_32_16(0x00)); @@ -130,16 +140,29 @@ library ERC4337Utils { /// @dev Returns the first section of `paymasterAndData` from the {PackedUserOperation}. function paymaster(PackedUserOperation calldata self) internal pure returns (address) { - return address(bytes20(self.paymasterAndData[0:20])); + return self.paymasterAndData.length < 52 ? address(0) : address(bytes20(self.paymasterAndData[0:20])); } /// @dev Returns the second section of `paymasterAndData` from the {PackedUserOperation}. function paymasterVerificationGasLimit(PackedUserOperation calldata self) internal pure returns (uint256) { - return uint128(bytes16(self.paymasterAndData[20:36])); + return self.paymasterAndData.length < 52 ? 0 : uint128(bytes16(self.paymasterAndData[20:36])); } /// @dev Returns the third section of `paymasterAndData` from the {PackedUserOperation}. function paymasterPostOpGasLimit(PackedUserOperation calldata self) internal pure returns (uint256) { - return uint128(bytes16(self.paymasterAndData[36:52])); + return self.paymasterAndData.length < 52 ? 0 : uint128(bytes16(self.paymasterAndData[36:52])); + } + + /// @dev Returns the forth section of `paymasterAndData` from the {PackedUserOperation}. + function paymasterData(PackedUserOperation calldata self) internal pure returns (bytes calldata) { + return self.paymasterAndData.length < 52 ? _emptyCalldataBytes() : self.paymasterAndData[52:]; + } + + // slither-disable-next-line write-after-write + function _emptyCalldataBytes() private pure returns (bytes calldata result) { + assembly ("memory-safe") { + result.offset := 0 + result.length := 0 + } } } diff --git a/test/account/utils/draft-ERC4337Utils.test.js b/test/account/utils/draft-ERC4337Utils.test.js index 5138a5d8ee8..96310ed83e3 100644 --- a/test/account/utils/draft-ERC4337Utils.test.js +++ b/test/account/utils/draft-ERC4337Utils.test.js @@ -2,13 +2,13 @@ const { ethers } = require('hardhat'); const { expect } = require('chai'); const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); -const { packValidationData, packPaymasterData, UserOperation } = require('../../helpers/erc4337'); +const { packValidationData, UserOperation } = require('../../helpers/erc4337'); const { MAX_UINT48 } = require('../../helpers/constants'); const fixture = async () => { - const [authorizer, sender, entrypoint, paymaster] = await ethers.getSigners(); + const [authorizer, sender, entrypoint, factory, paymaster] = await ethers.getSigners(); const utils = await ethers.deployContract('$ERC4337Utils'); - return { utils, authorizer, sender, entrypoint, paymaster }; + return { utils, authorizer, sender, entrypoint, factory, paymaster }; }; describe('ERC4337Utils', function () { @@ -144,6 +144,33 @@ describe('ERC4337Utils', function () { }); describe('userOp values', function () { + describe('intiCode', function () { + beforeEach(async function () { + this.userOp = new UserOperation({ + sender: this.sender, + nonce: 1, + verificationGas: 0x12345678n, + factory: this.factory, + factoryData: '0x123456', + }); + + this.emptyUserOp = new UserOperation({ + sender: this.sender, + nonce: 1, + }); + }); + + it('returns factory', async function () { + expect(this.utils.$factory(this.userOp.packed)).to.eventually.equal(this.factory); + expect(this.utils.$factory(this.emptyUserOp.packed)).to.eventually.equal(ethers.ZeroAddress); + }); + + it('returns factoryData', async function () { + expect(this.utils.$factoryData(this.userOp.packed)).to.eventually.equal('0x123456'); + expect(this.utils.$factoryData(this.emptyUserOp.packed)).to.eventually.equal('0x'); + }); + }); + it('returns verificationGasLimit', async function () { const userOp = new UserOperation({ sender: this.sender, nonce: 1, verificationGas: 0x12345678n }); expect(this.utils.$verificationGasLimit(userOp.packed)).to.eventually.equal(userOp.verificationGas); @@ -176,28 +203,43 @@ describe('ERC4337Utils', function () { describe('paymasterAndData', function () { beforeEach(async function () { - this.verificationGasLimit = 0x12345678n; - this.postOpGasLimit = 0x87654321n; - this.paymasterAndData = packPaymasterData(this.paymaster, this.verificationGasLimit, this.postOpGasLimit); this.userOp = new UserOperation({ sender: this.sender, nonce: 1, - paymasterAndData: this.paymasterAndData, + paymaster: this.paymaster, + paymasterVerificationGasLimit: 0x12345678n, + paymasterPostOpGasLimit: 0x87654321n, + paymasterData: '0xbeefcafe', + }); + + this.emptyUserOp = new UserOperation({ + sender: this.sender, + nonce: 1, }); }); it('returns paymaster', async function () { - expect(this.utils.$paymaster(this.userOp.packed)).to.eventually.equal(this.paymaster); + expect(this.utils.$paymaster(this.userOp.packed)).to.eventually.equal(this.userOp.paymaster); + expect(this.utils.$paymaster(this.emptyUserOp.packed)).to.eventually.equal(ethers.ZeroAddress); }); it('returns verificationGasLimit', async function () { expect(this.utils.$paymasterVerificationGasLimit(this.userOp.packed)).to.eventually.equal( - this.verificationGasLimit, + this.userOp.paymasterVerificationGasLimit, ); + expect(this.utils.$paymasterVerificationGasLimit(this.emptyUserOp.packed)).to.eventually.equal(0n); }); it('returns postOpGasLimit', async function () { - expect(this.utils.$paymasterPostOpGasLimit(this.userOp.packed)).to.eventually.equal(this.postOpGasLimit); + expect(this.utils.$paymasterPostOpGasLimit(this.userOp.packed)).to.eventually.equal( + this.userOp.paymasterPostOpGasLimit, + ); + expect(this.utils.$paymasterPostOpGasLimit(this.emptyUserOp.packed)).to.eventually.equal(0n); + }); + + it('returns data', async function () { + expect(this.utils.$paymasterData(this.userOp.packed)).to.eventually.equal(this.userOp.paymasterData); + expect(this.utils.$paymasterData(this.emptyUserOp.packed)).to.eventually.equal('0x'); }); }); }); diff --git a/test/account/utils/draft-ERC7579Utils.test.js b/test/account/utils/draft-ERC7579Utils.test.js index cc3b70425dd..e72b6698d60 100644 --- a/test/account/utils/draft-ERC7579Utils.test.js +++ b/test/account/utils/draft-ERC7579Utils.test.js @@ -1,6 +1,6 @@ const { ethers } = require('hardhat'); const { expect } = require('chai'); -const { loadFixture, setBalance } = require('@nomicfoundation/hardhat-network-helpers'); +const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); const { EXEC_TYPE_DEFAULT, EXEC_TYPE_TRY, @@ -17,11 +17,10 @@ const coder = ethers.AbiCoder.defaultAbiCoder(); const fixture = async () => { const [sender] = await ethers.getSigners(); - const utils = await ethers.deployContract('$ERC7579Utils'); + const utils = await ethers.deployContract('$ERC7579Utils', { value: ethers.parseEther('1') }); const utilsGlobal = await ethers.deployContract('$ERC7579UtilsGlobalMock'); const target = await ethers.deployContract('CallReceiverMock'); const anotherTarget = await ethers.deployContract('CallReceiverMock'); - await setBalance(utils.target, ethers.parseEther('1')); return { utils, utilsGlobal, target, anotherTarget, sender }; }; diff --git a/test/helpers/erc4337.js b/test/helpers/erc4337.js index 5901375b174..50a3b4a043b 100644 --- a/test/helpers/erc4337.js +++ b/test/helpers/erc4337.js @@ -26,10 +26,14 @@ function packValidationData(validAfter, validUntil, authorizer) { ); } -function packPaymasterData(paymaster, verificationGasLimit, postOpGasLimit) { +function packInitCode(factory, factoryData) { + return ethers.solidityPacked(['address', 'bytes'], [getAddress(factory), factoryData]); +} + +function packPaymasterAndData(paymaster, paymasterVerificationGasLimit, paymasterPostOpGasLimit, paymasterData) { return ethers.solidityPacked( - ['address', 'uint128', 'uint128'], - [getAddress(paymaster), verificationGasLimit, postOpGasLimit], + ['address', 'uint128', 'uint128', 'bytes'], + [getAddress(paymaster), paymasterVerificationGasLimit, paymasterPostOpGasLimit, paymasterData], ); } @@ -38,14 +42,18 @@ class UserOperation { constructor(params) { this.sender = getAddress(params.sender); this.nonce = params.nonce; - this.initCode = params.initCode ?? '0x'; + this.factory = params.factory ?? undefined; + this.factoryData = params.factoryData ?? '0x'; this.callData = params.callData ?? '0x'; this.verificationGas = params.verificationGas ?? 10_000_000n; this.callGas = params.callGas ?? 100_000n; this.preVerificationGas = params.preVerificationGas ?? 100_000n; this.maxPriorityFee = params.maxPriorityFee ?? 100_000n; this.maxFeePerGas = params.maxFeePerGas ?? 100_000n; - this.paymasterAndData = params.paymasterAndData ?? '0x'; + this.paymaster = params.paymaster ?? undefined; + this.paymasterVerificationGasLimit = params.paymasterVerificationGasLimit ?? 0n; + this.paymasterPostOpGasLimit = params.paymasterPostOpGasLimit ?? 0n; + this.paymasterData = params.paymasterData ?? '0x'; this.signature = params.signature ?? '0x'; } @@ -53,12 +61,19 @@ class UserOperation { return { sender: this.sender, nonce: this.nonce, - initCode: this.initCode, + initCode: this.factory ? packInitCode(this.factory, this.factoryData) : '0x', callData: this.callData, accountGasLimits: pack(this.verificationGas, this.callGas), preVerificationGas: this.preVerificationGas, gasFees: pack(this.maxPriorityFee, this.maxFeePerGas), - paymasterAndData: this.paymasterAndData, + paymasterAndData: this.paymaster + ? packPaymasterAndData( + this.paymaster, + this.paymasterVerificationGasLimit, + this.paymasterPostOpGasLimit, + this.paymasterData, + ) + : '0x', signature: this.signature, }; } @@ -90,6 +105,7 @@ module.exports = { SIG_VALIDATION_SUCCESS, SIG_VALIDATION_FAILURE, packValidationData, - packPaymasterData, + packInitCode, + packPaymasterAndData, UserOperation, }; From 2562c11f2566d7b027af18ad33d8153da65cbfd0 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Mon, 25 Nov 2024 18:43:21 +0100 Subject: [PATCH 60/84] Document VotesExtended assumptions (#5306) Co-authored-by: ernestognw --- contracts/governance/utils/VotesExtended.sol | 22 +++++++++++++++++++ ...ckpoints.test.js => VotesExtended.test.js} | 0 2 files changed, 22 insertions(+) rename test/governance/utils/{VotesAdditionalCheckpoints.test.js => VotesExtended.test.js} (100%) diff --git a/contracts/governance/utils/VotesExtended.sol b/contracts/governance/utils/VotesExtended.sol index 82e4624fb0d..b68ee2712e6 100644 --- a/contracts/governance/utils/VotesExtended.sol +++ b/contracts/governance/utils/VotesExtended.sol @@ -7,6 +7,28 @@ import {SafeCast} from "../../utils/math/SafeCast.sol"; /** * @dev Extension of {Votes} that adds checkpoints for delegations and balances. + * + * WARNING: While this contract extends {Votes}, valid uses of {Votes} may not be compatible with + * {VotesExtended} without additional considerations. This implementation of {_transferVotingUnits} must + * run AFTER the voting weight movement is registered, such that it is reflected on {_getVotingUnits}. + * + * Said differently, {VotesExtended} MUST be integrated in a way that calls {_transferVotingUnits} AFTER the + * asset transfer is registered and balances are updated: + * + * ```solidity + * contract VotingToken is Token, VotesExtended { + * function transfer(address from, address to, uint256 tokenId) public override { + * super.transfer(from, to, tokenId); // <- Perform the transfer first ... + * _transferVotingUnits(from, to, 1); // <- ... then call _transferVotingUnits. + * } + * + * function _getVotingUnits(address account) internal view override returns (uint256) { + * return balanceOf(account); + * } + * } + * ``` + * + * {ERC20Votes} and {ERC721Votes} follow this pattern and are thus safe to use with {VotesExtended}. */ abstract contract VotesExtended is Votes { using SafeCast for uint256; diff --git a/test/governance/utils/VotesAdditionalCheckpoints.test.js b/test/governance/utils/VotesExtended.test.js similarity index 100% rename from test/governance/utils/VotesAdditionalCheckpoints.test.js rename to test/governance/utils/VotesExtended.test.js From c3cb7a02954f6e7e8f4c2c97ed85b6462289c180 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Mon, 25 Nov 2024 18:47:55 +0100 Subject: [PATCH 61/84] Deduplicate logic in Votes.sol (#5314) Co-authored-by: Arr00 <13561405+arr00@users.noreply.github.com> --- contracts/governance/utils/Votes.sol | 21 ++++++++++---------- contracts/governance/utils/VotesExtended.sol | 17 ++++------------ 2 files changed, 15 insertions(+), 23 deletions(-) diff --git a/contracts/governance/utils/Votes.sol b/contracts/governance/utils/Votes.sol index bbbc2264ff9..976579719de 100644 --- a/contracts/governance/utils/Votes.sol +++ b/contracts/governance/utils/Votes.sol @@ -71,6 +71,15 @@ abstract contract Votes is Context, EIP712, Nonces, IERC5805 { return "mode=blocknumber&from=default"; } + /** + * @dev Validate that a timepoint is in the past, and return it as a uint48. + */ + function _validateTimepoint(uint256 timepoint) internal view returns (uint48) { + uint48 currentTimepoint = clock(); + if (timepoint >= currentTimepoint) revert ERC5805FutureLookup(timepoint, currentTimepoint); + return SafeCast.toUint48(timepoint); + } + /** * @dev Returns the current amount of votes that `account` has. */ @@ -87,11 +96,7 @@ abstract contract Votes is Context, EIP712, Nonces, IERC5805 { * - `timepoint` must be in the past. If operating using block numbers, the block must be already mined. */ function getPastVotes(address account, uint256 timepoint) public view virtual returns (uint256) { - uint48 currentTimepoint = clock(); - if (timepoint >= currentTimepoint) { - revert ERC5805FutureLookup(timepoint, currentTimepoint); - } - return _delegateCheckpoints[account].upperLookupRecent(SafeCast.toUint48(timepoint)); + return _delegateCheckpoints[account].upperLookupRecent(_validateTimepoint(timepoint)); } /** @@ -107,11 +112,7 @@ abstract contract Votes is Context, EIP712, Nonces, IERC5805 { * - `timepoint` must be in the past. If operating using block numbers, the block must be already mined. */ function getPastTotalSupply(uint256 timepoint) public view virtual returns (uint256) { - uint48 currentTimepoint = clock(); - if (timepoint >= currentTimepoint) { - revert ERC5805FutureLookup(timepoint, currentTimepoint); - } - return _totalCheckpoints.upperLookupRecent(SafeCast.toUint48(timepoint)); + return _totalCheckpoints.upperLookupRecent(_validateTimepoint(timepoint)); } /** diff --git a/contracts/governance/utils/VotesExtended.sol b/contracts/governance/utils/VotesExtended.sol index b68ee2712e6..27baaab84cf 100644 --- a/contracts/governance/utils/VotesExtended.sol +++ b/contracts/governance/utils/VotesExtended.sol @@ -31,7 +31,6 @@ import {SafeCast} from "../../utils/math/SafeCast.sol"; * {ERC20Votes} and {ERC721Votes} follow this pattern and are thus safe to use with {VotesExtended}. */ abstract contract VotesExtended is Votes { - using SafeCast for uint256; using Checkpoints for Checkpoints.Trace160; using Checkpoints for Checkpoints.Trace208; @@ -47,11 +46,7 @@ abstract contract VotesExtended is Votes { * - `timepoint` must be in the past. If operating using block numbers, the block must be already mined. */ function getPastDelegate(address account, uint256 timepoint) public view virtual returns (address) { - uint48 currentTimepoint = clock(); - if (timepoint >= currentTimepoint) { - revert ERC5805FutureLookup(timepoint, currentTimepoint); - } - return address(_delegateCheckpoints[account].upperLookupRecent(timepoint.toUint48())); + return address(_delegateCheckpoints[account].upperLookupRecent(_validateTimepoint(timepoint))); } /** @@ -63,11 +58,7 @@ abstract contract VotesExtended is Votes { * - `timepoint` must be in the past. If operating using block numbers, the block must be already mined. */ function getPastBalanceOf(address account, uint256 timepoint) public view virtual returns (uint256) { - uint48 currentTimepoint = clock(); - if (timepoint >= currentTimepoint) { - revert ERC5805FutureLookup(timepoint, currentTimepoint); - } - return _balanceOfCheckpoints[account].upperLookupRecent(timepoint.toUint48()); + return _balanceOfCheckpoints[account].upperLookupRecent(_validateTimepoint(timepoint)); } /// @inheritdoc Votes @@ -82,10 +73,10 @@ abstract contract VotesExtended is Votes { super._transferVotingUnits(from, to, amount); if (from != to) { if (from != address(0)) { - _balanceOfCheckpoints[from].push(clock(), _getVotingUnits(from).toUint208()); + _balanceOfCheckpoints[from].push(clock(), SafeCast.toUint208(_getVotingUnits(from))); } if (to != address(0)) { - _balanceOfCheckpoints[to].push(clock(), _getVotingUnits(to).toUint208()); + _balanceOfCheckpoints[to].push(clock(), SafeCast.toUint208(_getVotingUnits(to))); } } } From 0df841d2d75352786532e9e26f400efdef78d86e Mon Sep 17 00:00:00 2001 From: Arr00 <13561405+arr00@users.noreply.github.com> Date: Mon, 25 Nov 2024 15:31:30 -0500 Subject: [PATCH 62/84] Complete docstrings L-10 (#5315) --- contracts/account/utils/draft-ERC7579Utils.sol | 5 ++++- contracts/interfaces/draft-IERC4337.sol | 2 ++ contracts/interfaces/draft-IERC7579.sol | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/contracts/account/utils/draft-ERC7579Utils.sol b/contracts/account/utils/draft-ERC7579Utils.sol index 55fb7a09a8f..652f5554592 100644 --- a/contracts/account/utils/draft-ERC7579Utils.sol +++ b/contracts/account/utils/draft-ERC7579Utils.sol @@ -36,7 +36,10 @@ library ERC7579Utils { /// @dev Execution type that does not revert on failure. ExecType internal constant EXECTYPE_TRY = ExecType.wrap(0x01); - /// @dev Emits when an {EXECTYPE_TRY} execution fails. + /** + * @dev Emits when an {EXECTYPE_TRY} execution fails. + * @param batchExecutionIndex The index of the failed transaction in the execution batch. + */ event ERC7579TryExecuteFail(uint256 batchExecutionIndex, bytes result); /// @dev The provided {CallType} is not supported. diff --git a/contracts/interfaces/draft-IERC4337.sol b/contracts/interfaces/draft-IERC4337.sol index e98801ead39..5d765dbcde7 100644 --- a/contracts/interfaces/draft-IERC4337.sol +++ b/contracts/interfaces/draft-IERC4337.sol @@ -143,11 +143,13 @@ interface IEntryPoint is IEntryPointNonces, IEntryPointStake { /** * @dev Executes a batch of user operations. + * @param beneficiary Address to which gas is refunded up completing the execution. */ function handleOps(PackedUserOperation[] calldata ops, address payable beneficiary) external; /** * @dev Executes a batch of aggregated user operations per aggregator. + * @param beneficiary Address to which gas is refunded up completing the execution. */ function handleAggregatedOps( UserOpsPerAggregator[] calldata opsPerAggregator, diff --git a/contracts/interfaces/draft-IERC7579.sol b/contracts/interfaces/draft-IERC7579.sol index ae30e976709..61a862377f1 100644 --- a/contracts/interfaces/draft-IERC7579.sol +++ b/contracts/interfaces/draft-IERC7579.sol @@ -50,6 +50,7 @@ interface IERC7579Validator is IERC7579Module { * * MUST validate that the signature is a valid signature of the userOpHash * SHOULD return ERC-4337's SIG_VALIDATION_FAILED (and not revert) on signature mismatch + * See ERC-4337 for additional information on the return value */ function validateUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash) external returns (uint256); From 4afd59932910cdf9eb3f1e6a5945d84b03875658 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Tue, 26 Nov 2024 10:33:52 -0600 Subject: [PATCH 63/84] Document canonicalization in CAIP2 and CAIP10 libraries (#5319) --- contracts/utils/CAIP10.sol | 6 ++++++ contracts/utils/CAIP2.sol | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/contracts/utils/CAIP10.sol b/contracts/utils/CAIP10.sol index bd9ddbc9f17..f0424601735 100644 --- a/contracts/utils/CAIP10.sol +++ b/contracts/utils/CAIP10.sol @@ -13,6 +13,12 @@ import {CAIP2} from "./CAIP2.sol"; * account_id: chain_id + ":" + account_address * chain_id: [-a-z0-9]{3,8}:[-_a-zA-Z0-9]{1,32} (See {CAIP2}) * account_address: [-.%a-zA-Z0-9]{1,128} + * + * WARNING: According to [CAIP-10's canonicalization section](https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-10.md#canonicalization), + * the implementation remains at the developer's discretion. Please note that case variations may introduce ambiguity. + * For example, when building hashes to identify accounts or data associated to them, multiple representations of the + * same account would derive to different hashes. For EVM chains, we recommend using checksummed addresses for the + * "account_address" part. They can be generated onchain using {Strings-toChecksumHexString}. */ library CAIP10 { using Strings for address; diff --git a/contracts/utils/CAIP2.sol b/contracts/utils/CAIP2.sol index c6789ce4384..a7a69e6a873 100644 --- a/contracts/utils/CAIP2.sol +++ b/contracts/utils/CAIP2.sol @@ -12,6 +12,11 @@ import {Strings} from "./Strings.sol"; * chain_id: namespace + ":" + reference * namespace: [-a-z0-9]{3,8} * reference: [-_a-zA-Z0-9]{1,32} + * + * WARNING: In some cases, multiple CAIP-2 identifiers may all be valid representation of a single chain. + * For EVM chains, it is recommended to use `eip155:xxx` as the canonical representation (where `xxx` is + * the EIP-155 chain id). Consider the possible ambiguity when processing CAIP-2 identifiers or when using them + * in the context of hashes. */ library CAIP2 { using Strings for uint256; From fdf7012d3b1559f4a149e150b0dec5142279026c Mon Sep 17 00:00:00 2001 From: cairo Date: Tue, 26 Nov 2024 19:15:53 +0100 Subject: [PATCH 64/84] Optimize `log256`'s binary search (#5284) --- contracts/utils/math/Math.sol | 34 +++++++++++----------------------- 1 file changed, 11 insertions(+), 23 deletions(-) diff --git a/contracts/utils/math/Math.sol b/contracts/utils/math/Math.sol index cd55ff13085..2acd0754060 100644 --- a/contracts/utils/math/Math.sol +++ b/contracts/utils/math/Math.sol @@ -644,29 +644,17 @@ library Math { * * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string. */ - function log256(uint256 value) internal pure returns (uint256) { - uint256 result = 0; - uint256 isGt; - unchecked { - isGt = SafeCast.toUint(value > (1 << 128) - 1); - value >>= isGt * 128; - result += isGt * 16; - - isGt = SafeCast.toUint(value > (1 << 64) - 1); - value >>= isGt * 64; - result += isGt * 8; - - isGt = SafeCast.toUint(value > (1 << 32) - 1); - value >>= isGt * 32; - result += isGt * 4; - - isGt = SafeCast.toUint(value > (1 << 16) - 1); - value >>= isGt * 16; - result += isGt * 2; - - result += SafeCast.toUint(value > (1 << 8) - 1); - } - return result; + function log256(uint256 x) internal pure returns (uint256 r) { + // If value has upper 128 bits set, log2 result is at least 128 + r = SafeCast.toUint(x > 0xffffffffffffffffffffffffffffffff) << 7; + // If upper 64 bits of 128-bit half set, add 64 to result + r |= SafeCast.toUint((x >> r) > 0xffffffffffffffff) << 6; + // If upper 32 bits of 64-bit half set, add 32 to result + r |= SafeCast.toUint((x >> r) > 0xffffffff) << 5; + // If upper 16 bits of 32-bit half set, add 16 to result + r |= SafeCast.toUint((x >> r) > 0xffff) << 4; + // Add 1 if upper 8 bits of 16-bit half set, and divide accumulated result by 8 + return (r >> 3) | SafeCast.toUint((x >> r) > 0xff); } /** From 653963beb27f4dcda72ca2773f0a6bd7b733a6f4 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Wed, 27 Nov 2024 21:25:30 +0100 Subject: [PATCH 65/84] Various changes to code clarity (Fix N-07) (#5317) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernesto García Co-authored-by: Arr00 <13561405+arr00@users.noreply.github.com> --- contracts/account/utils/draft-ERC4337Utils.sol | 17 +++++++++-------- .../extensions/GovernorCountingOverridable.sol | 6 +++--- contracts/governance/utils/VotesExtended.sol | 14 +++++++------- contracts/proxy/Clones.sol | 8 ++++---- contracts/utils/NoncesKeyed.sol | 4 ++++ 5 files changed, 27 insertions(+), 22 deletions(-) diff --git a/contracts/account/utils/draft-ERC4337Utils.sol b/contracts/account/utils/draft-ERC4337Utils.sol index 2caa3bd2112..a5eec71b4d8 100644 --- a/contracts/account/utils/draft-ERC4337Utils.sol +++ b/contracts/account/utils/draft-ERC4337Utils.sol @@ -24,9 +24,9 @@ library ERC4337Utils { function parseValidationData( uint256 validationData ) internal pure returns (address aggregator, uint48 validAfter, uint48 validUntil) { - validAfter = uint48(bytes32(validationData).extract_32_6(0x00)); - validUntil = uint48(bytes32(validationData).extract_32_6(0x06)); - aggregator = address(bytes32(validationData).extract_32_20(0x0c)); + validAfter = uint48(bytes32(validationData).extract_32_6(0)); + validUntil = uint48(bytes32(validationData).extract_32_6(6)); + aggregator = address(bytes32(validationData).extract_32_20(12)); if (validUntil == 0) validUntil = type(uint48).max; } @@ -59,7 +59,8 @@ library ERC4337Utils { (address aggregator1, uint48 validAfter1, uint48 validUntil1) = parseValidationData(validationData1); (address aggregator2, uint48 validAfter2, uint48 validUntil2) = parseValidationData(validationData2); - bool success = aggregator1 == address(0) && aggregator2 == address(0); + bool success = aggregator1 == address(uint160(SIG_VALIDATION_SUCCESS)) && + aggregator2 == address(uint160(SIG_VALIDATION_SUCCESS)); uint48 validAfter = uint48(Math.max(validAfter1, validAfter2)); uint48 validUntil = uint48(Math.min(validUntil1, validUntil2)); return packValidationData(success, validAfter, validUntil); @@ -110,22 +111,22 @@ library ERC4337Utils { /// @dev Returns `verificationGasLimit` from the {PackedUserOperation}. function verificationGasLimit(PackedUserOperation calldata self) internal pure returns (uint256) { - return uint128(self.accountGasLimits.extract_32_16(0x00)); + return uint128(self.accountGasLimits.extract_32_16(0)); } /// @dev Returns `accountGasLimits` from the {PackedUserOperation}. function callGasLimit(PackedUserOperation calldata self) internal pure returns (uint256) { - return uint128(self.accountGasLimits.extract_32_16(0x10)); + return uint128(self.accountGasLimits.extract_32_16(16)); } /// @dev Returns the first section of `gasFees` from the {PackedUserOperation}. function maxPriorityFeePerGas(PackedUserOperation calldata self) internal pure returns (uint256) { - return uint128(self.gasFees.extract_32_16(0x00)); + return uint128(self.gasFees.extract_32_16(0)); } /// @dev Returns the second section of `gasFees` from the {PackedUserOperation}. function maxFeePerGas(PackedUserOperation calldata self) internal pure returns (uint256) { - return uint128(self.gasFees.extract_32_16(0x10)); + return uint128(self.gasFees.extract_32_16(16)); } /// @dev Returns the total gas price for the {PackedUserOperation} (ie. `maxFeePerGas` or `maxPriorityFeePerGas + basefee`). diff --git a/contracts/governance/extensions/GovernorCountingOverridable.sol b/contracts/governance/extensions/GovernorCountingOverridable.sol index e89fb49d5af..5b89c6ddfab 100644 --- a/contracts/governance/extensions/GovernorCountingOverridable.sol +++ b/contracts/governance/extensions/GovernorCountingOverridable.sol @@ -144,9 +144,9 @@ abstract contract GovernorCountingOverridable is GovernorVotes { revert GovernorAlreadyOverridenVote(account); } - uint256 proposalSnapshot = proposalSnapshot(proposalId); - uint256 overridenWeight = VotesExtended(address(token())).getPastBalanceOf(account, proposalSnapshot); - address delegate = VotesExtended(address(token())).getPastDelegate(account, proposalSnapshot); + uint256 snapshot = proposalSnapshot(proposalId); + uint256 overridenWeight = VotesExtended(address(token())).getPastBalanceOf(account, snapshot); + address delegate = VotesExtended(address(token())).getPastDelegate(account, snapshot); uint8 delegateCasted = proposalVote.voteReceipt[delegate].casted; proposalVote.voteReceipt[account].hasOverriden = true; diff --git a/contracts/governance/utils/VotesExtended.sol b/contracts/governance/utils/VotesExtended.sol index 27baaab84cf..70b0d92fb75 100644 --- a/contracts/governance/utils/VotesExtended.sol +++ b/contracts/governance/utils/VotesExtended.sol @@ -34,8 +34,8 @@ abstract contract VotesExtended is Votes { using Checkpoints for Checkpoints.Trace160; using Checkpoints for Checkpoints.Trace208; - mapping(address delegator => Checkpoints.Trace160) private _delegateCheckpoints; - mapping(address account => Checkpoints.Trace208) private _balanceOfCheckpoints; + mapping(address delegator => Checkpoints.Trace160) private _userDelegationCheckpoints; + mapping(address account => Checkpoints.Trace208) private _userVotingUnitsCheckpoints; /** * @dev Returns the delegate of an `account` at a specific moment in the past. If the `clock()` is @@ -46,7 +46,7 @@ abstract contract VotesExtended is Votes { * - `timepoint` must be in the past. If operating using block numbers, the block must be already mined. */ function getPastDelegate(address account, uint256 timepoint) public view virtual returns (address) { - return address(_delegateCheckpoints[account].upperLookupRecent(_validateTimepoint(timepoint))); + return address(_userDelegationCheckpoints[account].upperLookupRecent(_validateTimepoint(timepoint))); } /** @@ -58,14 +58,14 @@ abstract contract VotesExtended is Votes { * - `timepoint` must be in the past. If operating using block numbers, the block must be already mined. */ function getPastBalanceOf(address account, uint256 timepoint) public view virtual returns (uint256) { - return _balanceOfCheckpoints[account].upperLookupRecent(_validateTimepoint(timepoint)); + return _userVotingUnitsCheckpoints[account].upperLookupRecent(_validateTimepoint(timepoint)); } /// @inheritdoc Votes function _delegate(address account, address delegatee) internal virtual override { super._delegate(account, delegatee); - _delegateCheckpoints[account].push(clock(), uint160(delegatee)); + _userDelegationCheckpoints[account].push(clock(), uint160(delegatee)); } /// @inheritdoc Votes @@ -73,10 +73,10 @@ abstract contract VotesExtended is Votes { super._transferVotingUnits(from, to, amount); if (from != to) { if (from != address(0)) { - _balanceOfCheckpoints[from].push(clock(), SafeCast.toUint208(_getVotingUnits(from))); + _userVotingUnitsCheckpoints[from].push(clock(), SafeCast.toUint208(_getVotingUnits(from))); } if (to != address(0)) { - _balanceOfCheckpoints[to].push(clock(), SafeCast.toUint208(_getVotingUnits(to))); + _userVotingUnitsCheckpoints[to].push(clock(), SafeCast.toUint208(_getVotingUnits(to))); } } } diff --git a/contracts/proxy/Clones.sol b/contracts/proxy/Clones.sol index 6bef4b4845e..ed8fd6fab96 100644 --- a/contracts/proxy/Clones.sol +++ b/contracts/proxy/Clones.sol @@ -227,9 +227,9 @@ library Clones { * function should only be used to check addresses that are known to be clones. */ function fetchCloneArgs(address instance) internal view returns (bytes memory) { - bytes memory result = new bytes(instance.code.length - 0x2d); // revert if length is too short + bytes memory result = new bytes(instance.code.length - 45); // revert if length is too short assembly ("memory-safe") { - extcodecopy(instance, add(result, 0x20), 0x2d, mload(result)) + extcodecopy(instance, add(result, 32), 45, mload(result)) } return result; } @@ -248,11 +248,11 @@ library Clones { address implementation, bytes memory args ) private pure returns (bytes memory) { - if (args.length > 0x5fd3) revert CloneArgumentsTooLong(); + if (args.length > 24531) revert CloneArgumentsTooLong(); return abi.encodePacked( hex"61", - uint16(args.length + 0x2d), + uint16(args.length + 45), hex"3d81600a3d39f3363d3d373d3d3d363d73", implementation, hex"5af43d82803e903d91602b57fd5bf3", diff --git a/contracts/utils/NoncesKeyed.sol b/contracts/utils/NoncesKeyed.sol index 27e0685b3a3..31cd0704e15 100644 --- a/contracts/utils/NoncesKeyed.sol +++ b/contracts/utils/NoncesKeyed.sol @@ -7,6 +7,10 @@ import {Nonces} from "./Nonces.sol"; * @dev Alternative to {Nonces}, that supports key-ed nonces. * * Follows the https://eips.ethereum.org/EIPS/eip-4337#semi-abstracted-nonce-support[ERC-4337's semi-abstracted nonce system]. + * + * NOTE: This contract inherits from {Nonces} and reuses its storage for the first nonce key (i.e. `0`). This + * makes upgrading from {Nonces} to {NoncesKeyed} safe when using their upgradeable versions (e.g. `NoncesKeyedUpgradeable`). + * Doing so will NOT reset the current state of nonces, avoiding replay attacks where a nonce is reused after the upgrade. */ abstract contract NoncesKeyed is Nonces { mapping(address owner => mapping(uint192 key => uint64)) private _nonces; From 78be1b39aa6860f9bada089e0b77e5e99759ca75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Fri, 29 Nov 2024 10:53:54 -0600 Subject: [PATCH 66/84] Implement feedback for M-01, L-08, L-09 (#5324) Co-authored-by: Hadrien Croubois --- .../account/utils/draft-ERC7579Utils.sol | 5 ++- .../GovernorCountingOverridable.sol | 8 ++-- contracts/interfaces/draft-IERC4337.sol | 41 +++++++++++++++++-- contracts/interfaces/draft-IERC7579.sol | 7 ++-- contracts/proxy/Clones.sol | 2 +- contracts/token/ERC20/extensions/ERC1363.sol | 7 +++- contracts/utils/Strings.sol | 18 ++++---- test/account/utils/draft-ERC4337Utils.test.js | 34 ++++++++++++++- 8 files changed, 99 insertions(+), 23 deletions(-) diff --git a/contracts/account/utils/draft-ERC7579Utils.sol b/contracts/account/utils/draft-ERC7579Utils.sol index 652f5554592..dab18f26bf0 100644 --- a/contracts/account/utils/draft-ERC7579Utils.sol +++ b/contracts/account/utils/draft-ERC7579Utils.sol @@ -38,9 +38,10 @@ library ERC7579Utils { /** * @dev Emits when an {EXECTYPE_TRY} execution fails. - * @param batchExecutionIndex The index of the failed transaction in the execution batch. + * @param batchExecutionIndex The index of the failed call in the execution batch. + * @param returndata The returned data from the failed call. */ - event ERC7579TryExecuteFail(uint256 batchExecutionIndex, bytes result); + event ERC7579TryExecuteFail(uint256 batchExecutionIndex, bytes returndata); /// @dev The provided {CallType} is not supported. error ERC7579UnsupportedCallType(CallType callType); diff --git a/contracts/governance/extensions/GovernorCountingOverridable.sol b/contracts/governance/extensions/GovernorCountingOverridable.sol index 5b89c6ddfab..db375a93f47 100644 --- a/contracts/governance/extensions/GovernorCountingOverridable.sol +++ b/contracts/governance/extensions/GovernorCountingOverridable.sol @@ -8,7 +8,7 @@ import {VotesExtended} from "../utils/VotesExtended.sol"; import {GovernorVotes} from "./GovernorVotes.sol"; /** - * @dev Extension of {Governor} which enables delegatees to override the vote of their delegates. This module requires a + * @dev Extension of {Governor} which enables delegators to override the vote of their delegates. This module requires a * token that inherits {VotesExtended}. */ abstract contract GovernorCountingOverridable is GovernorVotes { @@ -162,7 +162,7 @@ abstract contract GovernorCountingOverridable is GovernorVotes { return overridenWeight; } - /// @dev Variant of {Governor-_castVote} that deals with vote overrides. + /// @dev Variant of {Governor-_castVote} that deals with vote overrides. Returns the overridden weight. function _castOverride( uint256 proposalId, address account, @@ -180,7 +180,7 @@ abstract contract GovernorCountingOverridable is GovernorVotes { return overridenWeight; } - /// @dev Public function for casting an override vote + /// @dev Public function for casting an override vote. Returns the overridden weight. function castOverrideVote( uint256 proposalId, uint8 support, @@ -190,7 +190,7 @@ abstract contract GovernorCountingOverridable is GovernorVotes { return _castOverride(proposalId, voter, support, reason); } - /// @dev Public function for casting an override vote using a voter's signature + /// @dev Public function for casting an override vote using a voter's signature. Returns the overridden weight. function castOverrideVoteBySig( uint256 proposalId, uint8 support, diff --git a/contracts/interfaces/draft-IERC4337.sol b/contracts/interfaces/draft-IERC4337.sol index 5d765dbcde7..67ab146c053 100644 --- a/contracts/interfaces/draft-IERC4337.sol +++ b/contracts/interfaces/draft-IERC4337.sol @@ -45,10 +45,18 @@ struct PackedUserOperation { /** * @dev Aggregates and validates multiple signatures for a batch of user operations. + * + * A contract could implement this interface with custom validation schemes that allow signature aggregation, + * enabling significant optimizations and gas savings for execution and transaction data cost. + * + * Bundlers and clients whitelist supported aggregators. + * + * See https://eips.ethereum.org/EIPS/eip-7766[ERC-7766] */ interface IAggregator { /** * @dev Validates the signature for a user operation. + * Returns an alternative signature that should be used during bundling. */ function validateUserOpSignature( PackedUserOperation calldata userOp @@ -73,6 +81,12 @@ interface IAggregator { /** * @dev Handle nonce management for accounts. + * + * Nonces are used in accounts as a replay protection mechanism and to ensure the order of user operations. + * To avoid limiting the number of operations an account can perform, the interface allows using parallel + * nonces by using a `key` parameter. + * + * See https://eips.ethereum.org/EIPS/eip-4337#semi-abstracted-nonce-support[ERC-4337 semi-abstracted nonce support]. */ interface IEntryPointNonces { /** @@ -84,7 +98,11 @@ interface IEntryPointNonces { } /** - * @dev Handle stake management for accounts. + * @dev Handle stake management for entities (i.e. accounts, paymasters, factories). + * + * The EntryPoint must implement the following API to let entities like paymasters have a stake, + * and thus have more flexibility in their storage access + * (see https://eips.ethereum.org/EIPS/eip-4337#reputation-scoring-and-throttlingbanning-for-global-entities[reputation, throttling and banning.]) */ interface IEntryPointStake { /** @@ -120,6 +138,8 @@ interface IEntryPointStake { /** * @dev Entry point for user operations. + * + * User operations are validated and executed by this contract. */ interface IEntryPoint is IEntryPointNonces, IEntryPointStake { /** @@ -158,11 +178,23 @@ interface IEntryPoint is IEntryPointNonces, IEntryPointStake { } /** - * @dev Base interface for an account. + * @dev Base interface for an ERC-4337 account. */ interface IAccount { /** * @dev Validates a user operation. + * + * * MUST validate the caller is a trusted EntryPoint + * * MUST validate that the signature is a valid signature of the userOpHash, and SHOULD + * return SIG_VALIDATION_FAILED (and not revert) on signature mismatch. Any other error MUST revert. + * * MUST pay the entryPoint (caller) at least the “missingAccountFunds” (which might + * be zero, in case the current account’s deposit is high enough) + * + * Returns an encoded packed validation data that is composed of the following elements: + * + * - `authorizer` (`address`): 0 for success, 1 for failure, otherwise the address of an authorizer contract + * - `validUntil` (`uint48`): The UserOp is valid only up to this time. Zero for “infinite”. + * - `validAfter` (`uint48`): The UserOp is valid only after this time. */ function validateUserOp( PackedUserOperation calldata userOp, @@ -195,7 +227,8 @@ interface IPaymaster { } /** - * @dev Validates whether the paymaster is willing to pay for the user operation. + * @dev Validates whether the paymaster is willing to pay for the user operation. See + * {IAccount-validateUserOp} for additional information on the return value. * * NOTE: Bundlers will reject this method if it modifies the state, unless it's whitelisted. */ @@ -207,6 +240,8 @@ interface IPaymaster { /** * @dev Verifies the sender is the entrypoint. + * @param actualGasCost the actual amount paid (by account or paymaster) for this UserOperation + * @param actualUserOpFeePerGas total gas used by this UserOperation (including preVerification, creation, validation and execution) */ function postOp( PostOpMode mode, diff --git a/contracts/interfaces/draft-IERC7579.sol b/contracts/interfaces/draft-IERC7579.sol index 61a862377f1..f97e33dc045 100644 --- a/contracts/interfaces/draft-IERC7579.sol +++ b/contracts/interfaces/draft-IERC7579.sol @@ -50,7 +50,7 @@ interface IERC7579Validator is IERC7579Module { * * MUST validate that the signature is a valid signature of the userOpHash * SHOULD return ERC-4337's SIG_VALIDATION_FAILED (and not revert) on signature mismatch - * See ERC-4337 for additional information on the return value + * See {IAccount-validateUserOp} for additional information on the return value */ function validateUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash) external returns (uint256); @@ -127,6 +127,7 @@ interface IERC7579Execution { * This function is intended to be called by Executor Modules * @param mode The encoded execution mode of the transaction. See ModeLib.sol for details * @param executionCalldata The encoded execution call data + * @return returnData An array with the returned data of each executed subcall * * MUST ensure adequate authorization control: i.e. onlyExecutorModule * If a mode is requested that is not supported by the Account, it MUST revert @@ -140,7 +141,7 @@ interface IERC7579Execution { /** * @dev ERC-7579 Account Config. * - * Accounts should implement this interface to exposes information that identifies the account, supported modules and capabilities. + * Accounts should implement this interface to expose information that identifies the account, supported modules and capabilities. */ interface IERC7579AccountConfig { /** @@ -174,7 +175,7 @@ interface IERC7579AccountConfig { /** * @dev ERC-7579 Module Config. * - * Accounts should implement this interface to allows installing and uninstalling modules. + * Accounts should implement this interface to allow installing and uninstalling modules. */ interface IERC7579ModuleConfig { event ModuleInstalled(uint256 moduleTypeId, address module); diff --git a/contracts/proxy/Clones.sol b/contracts/proxy/Clones.sol index ed8fd6fab96..5bd45a0864b 100644 --- a/contracts/proxy/Clones.sol +++ b/contracts/proxy/Clones.sol @@ -163,7 +163,7 @@ library Clones { * access the arguments within the implementation, use {fetchCloneArgs}. * * This function uses the create2 opcode and a `salt` to deterministically deploy the clone. Using the same - * `implementation`, `args` and `salt` multiple time will revert, since the clones cannot be deployed twice + * `implementation`, `args` and `salt` multiple times will revert, since the clones cannot be deployed twice * at the same address. */ function cloneDeterministicWithImmutableArgs( diff --git a/contracts/token/ERC20/extensions/ERC1363.sol b/contracts/token/ERC20/extensions/ERC1363.sol index acc841d78fc..952582a8404 100644 --- a/contracts/token/ERC20/extensions/ERC1363.sol +++ b/contracts/token/ERC20/extensions/ERC1363.sol @@ -48,7 +48,8 @@ abstract contract ERC1363 is ERC20, ERC165, IERC1363 { /** * @dev Moves a `value` amount of tokens from the caller's account to `to` - * and then calls {IERC1363Receiver-onTransferReceived} on `to`. + * and then calls {IERC1363Receiver-onTransferReceived} on `to`. Returns a flag that indicates + * if the call succeeded. * * Requirements: * @@ -75,7 +76,8 @@ abstract contract ERC1363 is ERC20, ERC165, IERC1363 { /** * @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism - * and then calls {IERC1363Receiver-onTransferReceived} on `to`. + * and then calls {IERC1363Receiver-onTransferReceived} on `to`. Returns a flag that indicates + * if the call succeeded. * * Requirements: * @@ -108,6 +110,7 @@ abstract contract ERC1363 is ERC20, ERC165, IERC1363 { /** * @dev Sets a `value` amount of tokens as the allowance of `spender` over the * caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`. + * Returns a flag that indicates if the call succeeded. * * Requirements: * diff --git a/contracts/utils/Strings.sol b/contracts/utils/Strings.sol index 161a2b576d7..f9465eaf04d 100644 --- a/contracts/utils/Strings.sol +++ b/contracts/utils/Strings.sol @@ -177,7 +177,8 @@ library Strings { } /** - * @dev Variant of {tryParseUint} that does not check bounds and returns (true, 0) if they are invalid. + * @dev Implementation of {tryParseUint} that does not check bounds. Caller should make sure that + * `begin <= end <= input.length`. Other inputs would result in undefined behavior. */ function _tryParseUintUncheckedBounds( string memory input, @@ -249,7 +250,8 @@ library Strings { } /** - * @dev Variant of {tryParseInt} that does not check bounds and returns (true, 0) if they are invalid. + * @dev Implementation of {tryParseInt} that does not check bounds. Caller should make sure that + * `begin <= end <= input.length`. Other inputs would result in undefined behavior. */ function _tryParseIntUncheckedBounds( string memory input, @@ -323,7 +325,8 @@ library Strings { } /** - * @dev Variant of {tryParseHexUint} that does not check bounds and returns (true, 0) if they are invalid. + * @dev Implementation of {tryParseHexUint} that does not check bounds. Caller should make sure that + * `begin <= end <= input.length`. Other inputs would result in undefined behavior. */ function _tryParseHexUintUncheckedBounds( string memory input, @@ -333,7 +336,7 @@ library Strings { bytes memory buffer = bytes(input); // skip 0x prefix if present - bool hasPrefix = (begin < end + 1) && bytes2(_unsafeReadBytesOffset(buffer, begin)) == bytes2("0x"); // don't do out-of-bound (possibly unsafe) read if sub-string is empty + bool hasPrefix = (end > begin + 1) && bytes2(_unsafeReadBytesOffset(buffer, begin)) == bytes2("0x"); // don't do out-of-bound (possibly unsafe) read if sub-string is empty uint256 offset = hasPrefix.toUint() * 2; uint256 result = 0; @@ -390,12 +393,13 @@ library Strings { uint256 begin, uint256 end ) internal pure returns (bool success, address value) { - // check that input is the correct length - bool hasPrefix = (begin < end + 1) && bytes2(_unsafeReadBytesOffset(bytes(input), begin)) == bytes2("0x"); // don't do out-of-bound (possibly unsafe) read if sub-string is empty + if (end > bytes(input).length || begin > end) return (false, address(0)); + bool hasPrefix = (end > begin + 1) && bytes2(_unsafeReadBytesOffset(bytes(input), begin)) == bytes2("0x"); // don't do out-of-bound (possibly unsafe) read if sub-string is empty uint256 expectedLength = 40 + hasPrefix.toUint() * 2; - if (end - begin == expectedLength && end <= bytes(input).length) { + // check that input is the correct length + if (end - begin == expectedLength) { // length guarantees that this does not overflow, and value is at most type(uint160).max (bool s, uint256 v) = _tryParseHexUintUncheckedBounds(input, begin, end); return (s, address(uint160(v))); diff --git a/test/account/utils/draft-ERC4337Utils.test.js b/test/account/utils/draft-ERC4337Utils.test.js index 96310ed83e3..ab2500b5382 100644 --- a/test/account/utils/draft-ERC4337Utils.test.js +++ b/test/account/utils/draft-ERC4337Utils.test.js @@ -4,11 +4,14 @@ const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); const { packValidationData, UserOperation } = require('../../helpers/erc4337'); const { MAX_UINT48 } = require('../../helpers/constants'); +const ADDRESS_ONE = '0x0000000000000000000000000000000000000001'; const fixture = async () => { const [authorizer, sender, entrypoint, factory, paymaster] = await ethers.getSigners(); const utils = await ethers.deployContract('$ERC4337Utils'); - return { utils, authorizer, sender, entrypoint, factory, paymaster }; + const SIG_VALIDATION_SUCCESS = await utils.$SIG_VALIDATION_SUCCESS(); + const SIG_VALIDATION_FAILED = await utils.$SIG_VALIDATION_FAILED(); + return { utils, authorizer, sender, entrypoint, factory, paymaster, SIG_VALIDATION_SUCCESS, SIG_VALIDATION_FAILED }; }; describe('ERC4337Utils', function () { @@ -41,6 +44,20 @@ describe('ERC4337Utils', function () { MAX_UINT48, ]); }); + + it('parse canonical values', async function () { + expect(this.utils.$parseValidationData(this.SIG_VALIDATION_SUCCESS)).to.eventually.deep.equal([ + ethers.ZeroAddress, + 0n, + MAX_UINT48, + ]); + + expect(this.utils.$parseValidationData(this.SIG_VALIDATION_FAILED)).to.eventually.deep.equal([ + ADDRESS_ONE, + 0n, + MAX_UINT48, + ]); + }); }); describe('packValidationData', function () { @@ -65,6 +82,21 @@ describe('ERC4337Utils', function () { validationData, ); }); + + it('packing reproduced canonical values', async function () { + expect(this.utils.$packValidationData(ethers.Typed.address(ethers.ZeroAddress), 0n, 0n)).to.eventually.equal( + this.SIG_VALIDATION_SUCCESS, + ); + expect(this.utils.$packValidationData(ethers.Typed.bool(true), 0n, 0n)).to.eventually.equal( + this.SIG_VALIDATION_SUCCESS, + ); + expect(this.utils.$packValidationData(ethers.Typed.address(ADDRESS_ONE), 0n, 0n)).to.eventually.equal( + this.SIG_VALIDATION_FAILED, + ); + expect(this.utils.$packValidationData(ethers.Typed.bool(false), 0n, 0n)).to.eventually.equal( + this.SIG_VALIDATION_FAILED, + ); + }); }); describe('combineValidationData', function () { From a3a6db86d5b4d7ef003b6f71e6504867d2679a7f Mon Sep 17 00:00:00 2001 From: Simka <0xsimka@gmail.com> Date: Mon, 2 Dec 2024 16:48:25 +0300 Subject: [PATCH 67/84] Fix typo in ERC4337Utils.paymasterData comments (#5333) --- contracts/account/utils/draft-ERC4337Utils.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/account/utils/draft-ERC4337Utils.sol b/contracts/account/utils/draft-ERC4337Utils.sol index a5eec71b4d8..78100b0c96e 100644 --- a/contracts/account/utils/draft-ERC4337Utils.sol +++ b/contracts/account/utils/draft-ERC4337Utils.sol @@ -154,7 +154,7 @@ library ERC4337Utils { return self.paymasterAndData.length < 52 ? 0 : uint128(bytes16(self.paymasterAndData[36:52])); } - /// @dev Returns the forth section of `paymasterAndData` from the {PackedUserOperation}. + /// @dev Returns the fourth section of `paymasterAndData` from the {PackedUserOperation}. function paymasterData(PackedUserOperation calldata self) internal pure returns (bytes calldata) { return self.paymasterAndData.length < 52 ? _emptyCalldataBytes() : self.paymasterAndData[52:]; } From 6e05b68bd96ab90a4049adb89f20fd578404b274 Mon Sep 17 00:00:00 2001 From: ebulku Date: Wed, 4 Dec 2024 00:30:56 +0100 Subject: [PATCH 68/84] Remove token value mention in ERC-1155 documentation (#5336) --- docs/modules/ROOT/pages/erc1155.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/modules/ROOT/pages/erc1155.adoc b/docs/modules/ROOT/pages/erc1155.adoc index 7f00f3ea4b1..2c31db8b86c 100644 --- a/docs/modules/ROOT/pages/erc1155.adoc +++ b/docs/modules/ROOT/pages/erc1155.adoc @@ -106,7 +106,7 @@ A key difference when using xref:api:token/ERC1155.adoc#IERC1155-safeTransferFro ERC1155InvalidReceiver("
") ---- -This is a good thing! It means that the recipient contract has not registered itself as aware of the ERC-1155 protocol, so transfers to it are disabled to *prevent tokens from being locked forever*. As an example, https://etherscan.io/token/0xa74476443119A942dE498590Fe1f2454d7D4aC0d?a=0xa74476443119A942dE498590Fe1f2454d7D4aC0d[the Golem contract currently holds over 350k `GNT` tokens], worth multiple tens of thousands of dollars, and lacks methods to get them out of there. This has happened to virtually every ERC20-backed project, usually due to user error. +This is a good thing! It means that the recipient contract has not registered itself as aware of the ERC-1155 protocol, so transfers to it are disabled to *prevent tokens from being locked forever*. As an example, https://etherscan.io/token/0xa74476443119A942dE498590Fe1f2454d7D4aC0d?a=0xa74476443119A942dE498590Fe1f2454d7D4aC0d[the Golem contract currently holds over 350k `GNT` tokens], and lacks methods to get them out of there. This has happened to virtually every ERC20-backed project, usually due to user error. In order for our contract to receive ERC-1155 tokens we can inherit from the convenience contract xref:api:token/ERC1155.adoc#ERC1155Holder[`ERC1155Holder`] which handles the registering for us. However, we need to remember to implement functionality to allow tokens to be transferred out of our contract: From 779c27ad36b894a93f7c26240ce9cee0864ccf80 Mon Sep 17 00:00:00 2001 From: Arr00 <13561405+arr00@users.noreply.github.com> Date: Thu, 5 Dec 2024 14:48:49 -0500 Subject: [PATCH 69/84] Cherry-pick "Fix v5.2 testing" from the release-v5.2 branch (#5342) Co-authored-by: Hadrien Croubois --- .../governance/extensions/GovernorCountingOverridable.test.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/governance/extensions/GovernorCountingOverridable.test.js b/test/governance/extensions/GovernorCountingOverridable.test.js index 92e86f45019..32ee47439f3 100644 --- a/test/governance/extensions/GovernorCountingOverridable.test.js +++ b/test/governance/extensions/GovernorCountingOverridable.test.js @@ -269,7 +269,9 @@ describe('GovernorCountingOverridable', function () { }); it('can not vote twice', async function () { - await expect(this.mock.connect(this.voter1).castVote(this.helper.id, VoteType.Against)); + await expect(this.mock.connect(this.voter1).castVote(this.helper.id, VoteType.Against)) + .to.emit(this.mock, 'VoteCast') + .withArgs(this.voter1, this.helper.id, VoteType.Against, ethers.parseEther('5'), ''); await expect(this.mock.connect(this.voter1).castVote(this.helper.id, VoteType.Abstain)) .to.be.revertedWithCustomError(this.mock, 'GovernorAlreadyCastVote') .withArgs(this.voter1.address); From 063fbb3a1fd7aeb5bbb0dbb0bbba780820b9833b Mon Sep 17 00:00:00 2001 From: Cypher Pepe <125112044+cypherpepe@users.noreply.github.com> Date: Fri, 6 Dec 2024 00:30:23 +0300 Subject: [PATCH 70/84] Fix typo in ERC721 API reference docs (#5329) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernesto García --- contracts/token/ERC721/README.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/token/ERC721/README.adoc b/contracts/token/ERC721/README.adoc index 5554720d699..b5aa6579808 100644 --- a/contracts/token/ERC721/README.adoc +++ b/contracts/token/ERC721/README.adoc @@ -22,7 +22,7 @@ OpenZeppelin Contracts provides implementations of all four interfaces: Additionally there are a few of other extensions: -* {ERC721Consecutive}: An implementation of https://eips.ethereum.org/EIPS/eip-2309[ERC-2309] for minting batchs of tokens during construction, in accordance with ERC-721. +* {ERC721Consecutive}: An implementation of https://eips.ethereum.org/EIPS/eip-2309[ERC-2309] for minting batches of tokens during construction, in accordance with ERC-721. * {ERC721URIStorage}: A more flexible but more expensive way of storing metadata. * {ERC721Votes}: Support for voting and vote delegation. * {ERC721Royalty}: A way to signal royalty information following ERC-2981. From 8829465a08cac423dcf59852f21e448449c1a1a8 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Fri, 6 Dec 2024 09:49:07 +0100 Subject: [PATCH 71/84] Run linter on commit instead of push (#5340) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Arr00 <13561405+arr00@users.noreply.github.com> Co-authored-by: Ernesto García --- .githooks/pre-push | 8 - .husky/pre-commit | 2 + package-lock.json | 693 ++++++++++++++++++++++++++++++++++++++++++++- package.json | 16 +- scripts/prepare.sh | 5 - 5 files changed, 705 insertions(+), 19 deletions(-) delete mode 100755 .githooks/pre-push create mode 100755 .husky/pre-commit delete mode 100755 scripts/prepare.sh diff --git a/.githooks/pre-push b/.githooks/pre-push deleted file mode 100755 index f028ce58e0b..00000000000 --- a/.githooks/pre-push +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -if [ "${CI:-"false"}" != "true" ]; then - npm run test:generation - npm run lint -fi diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 00000000000..4738b05554b --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,2 @@ +npm run test:generation +npx lint-staged diff --git a/package-lock.json b/package-lock.json index bd5171b13f4..06abf5c16fd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,6 +32,8 @@ "hardhat-exposed": "^0.3.15", "hardhat-gas-reporter": "^2.0.0", "hardhat-ignore-warnings": "^0.2.11", + "husky": "^9.1.7", + "lint-staged": "^15.2.10", "lodash.startcase": "^4.4.0", "micromatch": "^4.0.2", "p-limit": "^6.0.0", @@ -3587,6 +3589,65 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "dev": true, + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", + "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", + "dev": true, + "dependencies": { + "slice-ansi": "^5.0.0", + "string-width": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/cli-truncate/node_modules/slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -3648,6 +3709,12 @@ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "dev": true }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -4145,6 +4212,18 @@ "node": ">=6" } }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -4964,6 +5043,12 @@ "npm": ">=3" } }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "dev": true + }, "node_modules/evp_bytestokey": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", @@ -4974,6 +5059,53 @@ "safe-buffer": "^5.1.1" } }, + "node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/execa/node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/execa/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/extendable-error": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/extendable-error/-/extendable-error-0.1.7.tgz", @@ -5286,6 +5418,18 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-east-asian-width": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz", + "integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-func-name": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", @@ -6475,6 +6619,30 @@ "integrity": "sha512-UNopramDEhHJD+VR+ehk8rOslwSfByxPIZyJRfV739NDhN5LF1fa1MqnzKm2lGTQRjNrjK19Q5fhkgIfjlVUKw==", "dev": true }, + "node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/husky": { + "version": "9.1.7", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", + "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", + "dev": true, + "bin": { + "husky": "bin.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -6707,6 +6875,18 @@ "node": ">=0.10.0" } }, + "node_modules/is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -6811,6 +6991,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-string": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", @@ -7094,12 +7286,168 @@ "node": ">= 0.8.0" } }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "dev": true }, + "node_modules/lint-staged": { + "version": "15.2.10", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.10.tgz", + "integrity": "sha512-5dY5t743e1byO19P9I4b3x8HJwalIznL5E1FWYnU6OWw33KxNBSLAc6Cy7F2PsFEO8FKnLwjwm5hx7aMF0jzZg==", + "dev": true, + "dependencies": { + "chalk": "~5.3.0", + "commander": "~12.1.0", + "debug": "~4.3.6", + "execa": "~8.0.1", + "lilconfig": "~3.1.2", + "listr2": "~8.2.4", + "micromatch": "~4.0.8", + "pidtree": "~0.6.0", + "string-argv": "~0.3.2", + "yaml": "~2.5.0" + }, + "bin": { + "lint-staged": "bin/lint-staged.js" + }, + "engines": { + "node": ">=18.12.0" + }, + "funding": { + "url": "https://opencollective.com/lint-staged" + } + }, + "node_modules/lint-staged/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/lint-staged/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/lint-staged/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/lint-staged/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/listr2": { + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.2.5.tgz", + "integrity": "sha512-iyAZCeyD+c1gPyE9qpFu8af0Y+MRtmKOncdGoA2S5EY8iFq99dmmvkNnHiWo+pj0s7yH7l3KPIgee77tKpXPWQ==", + "dev": true, + "dependencies": { + "cli-truncate": "^4.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.1.0", + "rfdc": "^1.4.1", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/listr2/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/listr2/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/listr2/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/listr2/node_modules/wrap-ansi": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", + "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/load-yaml-file": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/load-yaml-file/-/load-yaml-file-0.2.0.tgz", @@ -7243,6 +7591,127 @@ "node": ">=8" } }, + "node_modules/log-update": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", + "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", + "dev": true, + "dependencies": { + "ansi-escapes": "^7.0.0", + "cli-cursor": "^5.0.0", + "slice-ansi": "^7.1.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-escapes": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz", + "integrity": "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==", + "dev": true, + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/log-update/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-update/node_modules/is-fullwidth-code-point": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz", + "integrity": "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==", + "dev": true, + "dependencies": { + "get-east-asian-width": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/slice-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.0.tgz", + "integrity": "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/wrap-ansi": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", + "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/loupe": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz", @@ -7357,6 +7826,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -7373,12 +7848,12 @@ "dev": true }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -7406,6 +7881,30 @@ "node": ">= 0.6" } }, + "node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/mimic-response": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", @@ -7963,6 +8462,33 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "dev": true, + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/number-to-bn": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/number-to-bn/-/number-to-bn-1.7.0.tgz", @@ -8034,6 +8560,21 @@ "wrappy": "1" } }, + "node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/optionator": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", @@ -8320,6 +8861,18 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pidtree": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", + "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", + "dev": true, + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/pify": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", @@ -8861,6 +9414,49 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dev": true, + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor/node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", @@ -8880,6 +9476,12 @@ "node": ">=0.10.0" } }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true + }, "node_modules/rimraf": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.0.tgz", @@ -10218,6 +10820,32 @@ "safe-buffer": "~5.2.0" } }, + "node_modules/string-argv": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", + "dev": true, + "engines": { + "node": ">=0.6.19" + } + }, + "node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/string-width-cjs": { "name": "string-width", "version": "4.2.3", @@ -10242,6 +10870,39 @@ "node": ">=8" } }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/string-width/node_modules/emoji-regex": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "dev": true + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/string.prototype.trim": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz", @@ -10321,6 +10982,18 @@ "node": ">=4" } }, + "node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/strip-hex-prefix": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-hex-prefix/-/strip-hex-prefix-1.0.0.tgz", @@ -11367,6 +12040,18 @@ "node": ">=10" } }, + "node_modules/yaml": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.1.tgz", + "integrity": "sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==", + "dev": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", diff --git a/package.json b/package.json index 44c697ac7ef..e798697d677 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "coverage": "scripts/checks/coverage.sh", "docs": "npm run prepare-docs && oz-docs", "docs:watch": "oz-docs watch contracts docs/templates docs/config.js", - "prepare": "scripts/prepare.sh", + "prepare": "husky", "prepare-docs": "scripts/prepare-docs.sh", "lint": "npm run lint:js && npm run lint:sol", "lint:fix": "npm run lint:js:fix && npm run lint:sol:fix", @@ -67,13 +67,15 @@ "eslint": "^9.0.0", "eslint-config-prettier": "^9.0.0", "ethers": "^6.7.1", - "globals": "^15.3.0", "glob": "^11.0.0", + "globals": "^15.3.0", "graphlib": "^2.1.8", "hardhat": "^2.22.2", "hardhat-exposed": "^0.3.15", "hardhat-gas-reporter": "^2.0.0", "hardhat-ignore-warnings": "^0.2.11", + "husky": "^9.1.7", + "lint-staged": "^15.2.10", "lodash.startcase": "^4.4.0", "micromatch": "^4.0.2", "p-limit": "^6.0.0", @@ -88,5 +90,15 @@ "solidity-docgen": "^0.6.0-beta.29", "undici": "^6.11.1", "yargs": "^17.0.0" + }, + "lint-staged": { + "*.{js,ts}": [ + "prettier --log-level warn --ignore-path .gitignore --check", + "eslint" + ], + "*.sol": [ + "prettier --log-level warn --ignore-path .gitignore --check", + "solhint" + ] } } diff --git a/scripts/prepare.sh b/scripts/prepare.sh deleted file mode 100755 index a7d74227d26..00000000000 --- a/scripts/prepare.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -if git status &>/dev/null; then git config core.hooksPath .githooks; fi From 0643d17e8d9640df803c5d256c53d458c4c6008d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2024 18:47:26 +0100 Subject: [PATCH 72/84] Bump ws, ethers and hardhat-gas-reporter (#5345) --- package-lock.json | 114 ++++++++++-------- package.json | 4 +- .../GovernorCountingFractional.test.js | 2 +- .../extensions/GovernorERC721.test.js | 2 +- .../GovernorPreventLateQuorum.test.js | 2 +- .../extensions/GovernorStorage.test.js | 2 +- .../extensions/GovernorTimelockAccess.test.js | 2 +- .../GovernorTimelockCompound.test.js | 2 +- .../GovernorTimelockControl.test.js | 2 +- .../GovernorVotesQuorumFraction.test.js | 2 +- .../extensions/GovernorWithParams.test.js | 2 +- test/metatx/ERC2771Context.test.js | 2 +- 12 files changed, 77 insertions(+), 61 deletions(-) diff --git a/package-lock.json b/package-lock.json index 06abf5c16fd..79968635cd5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,13 +24,13 @@ "chai": "^4.2.0", "eslint": "^9.0.0", "eslint-config-prettier": "^9.0.0", - "ethers": "^6.7.1", + "ethers": "^6.13.4", "glob": "^11.0.0", "globals": "^15.3.0", "graphlib": "^2.1.8", "hardhat": "^2.22.2", "hardhat-exposed": "^0.3.15", - "hardhat-gas-reporter": "^2.0.0", + "hardhat-gas-reporter": "^2.1.0", "hardhat-ignore-warnings": "^0.2.11", "husky": "^9.1.7", "lint-staged": "^15.2.10", @@ -60,10 +60,11 @@ } }, "node_modules/@adraffy/ens-normalize": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.9.2.tgz", - "integrity": "sha512-0h+FrQDqe2Wn+IIGFkTCd4aAwTJ+7834Ek1COohCyV26AXhwQ7WQaz+4F/nLOeVl/3BtWHOHLPsq46V8YB46Eg==", - "dev": true + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz", + "integrity": "sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==", + "dev": true, + "license": "MIT" }, "node_modules/@babel/code-frame": { "version": "7.22.13", @@ -2611,12 +2612,13 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.9.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.0.tgz", - "integrity": "sha512-nekiGu2NDb1BcVofVcEKMIwzlx4NjHlcjhoxxKBNLtz15Y1z7MYf549DFvkHSId02Ax6kGwWntIBPC3l/JZcmw==", + "version": "22.7.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz", + "integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==", "dev": true, + "license": "MIT", "dependencies": { - "undici-types": "~5.26.4" + "undici-types": "~6.19.2" } }, "node_modules/@types/normalize-package-data": { @@ -4931,9 +4933,9 @@ "dev": true }, "node_modules/ethers": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.7.1.tgz", - "integrity": "sha512-qX5kxIFMfg1i+epfgb0xF4WM7IqapIIu50pOJ17aebkxxa4BacW5jFrQRmCJpDEg2ZK2oNtR5QjrQ1WDBF29dA==", + "version": "6.13.4", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.13.4.tgz", + "integrity": "sha512-21YtnZVg4/zKkCQPjrDj38B1r4nQvTZLopUGMLQ1ePU2zV/joCfDC3t3iKQjWRzjjjbzR+mdAIoikeBRNkdllA==", "dev": true, "funding": [ { @@ -4945,36 +4947,45 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], + "license": "MIT", "dependencies": { - "@adraffy/ens-normalize": "1.9.2", - "@noble/hashes": "1.1.2", - "@noble/secp256k1": "1.7.1", - "@types/node": "18.15.13", + "@adraffy/ens-normalize": "1.10.1", + "@noble/curves": "1.2.0", + "@noble/hashes": "1.3.2", + "@types/node": "22.7.5", "aes-js": "4.0.0-beta.5", - "tslib": "2.4.0", - "ws": "8.5.0" + "tslib": "2.7.0", + "ws": "8.17.1" }, "engines": { "node": ">=14.0.0" } }, - "node_modules/ethers/node_modules/@noble/hashes": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.1.2.tgz", - "integrity": "sha512-KYRCASVTv6aeUi1tsF8/vpyR7zpfs3FUzy2Jqm+MU+LmUKhQ0y2FpfwqkCcxSg2ua4GALJd8k2R76WxwZGbQpA==", + "node_modules/ethers/node_modules/@noble/curves": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", + "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ] + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.3.2" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } }, - "node_modules/ethers/node_modules/@types/node": { - "version": "18.15.13", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.13.tgz", - "integrity": "sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q==", - "dev": true + "node_modules/ethers/node_modules/@noble/hashes": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", + "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } }, "node_modules/ethers/node_modules/aes-js": { "version": "4.0.0-beta.5", @@ -4983,22 +4994,24 @@ "dev": true }, "node_modules/ethers/node_modules/tslib": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", - "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", - "dev": true + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true, + "license": "0BSD" }, "node_modules/ethers/node_modules/ws": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz", - "integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=10.0.0" }, "peerDependencies": { "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" + "utf-8-validate": ">=5.0.2" }, "peerDependenciesMeta": { "bufferutil": { @@ -5828,6 +5841,7 @@ "resolved": "https://registry.npmjs.org/hardhat-gas-reporter/-/hardhat-gas-reporter-2.1.0.tgz", "integrity": "sha512-d/WU/qHhBFnbweAm2fAAjcaaE0M7BKZ4r+/bqcFlfP6um28BXtlv2FrJ6oyQUGSFD0ttbmB7sH4ZFDzkYw5GzA==", "dev": true, + "license": "MIT", "dependencies": { "@ethersproject/abi": "^5.7.0", "@ethersproject/bytes": "^5.7.0", @@ -11489,10 +11503,11 @@ } }, "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true, + "license": "MIT" }, "node_modules/universalify": { "version": "0.1.2", @@ -12011,10 +12026,11 @@ "dev": true }, "node_modules/ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8.3.0" }, diff --git a/package.json b/package.json index e798697d677..db00d612deb 100644 --- a/package.json +++ b/package.json @@ -66,13 +66,13 @@ "chai": "^4.2.0", "eslint": "^9.0.0", "eslint-config-prettier": "^9.0.0", - "ethers": "^6.7.1", + "ethers": "^6.13.4", "glob": "^11.0.0", "globals": "^15.3.0", "graphlib": "^2.1.8", "hardhat": "^2.22.2", "hardhat-exposed": "^0.3.15", - "hardhat-gas-reporter": "^2.0.0", + "hardhat-gas-reporter": "^2.1.0", "hardhat-ignore-warnings": "^0.2.11", "husky": "^9.1.7", "lint-staged": "^15.2.10", diff --git a/test/governance/extensions/GovernorCountingFractional.test.js b/test/governance/extensions/GovernorCountingFractional.test.js index 393dbad79d5..a46de210b93 100644 --- a/test/governance/extensions/GovernorCountingFractional.test.js +++ b/test/governance/extensions/GovernorCountingFractional.test.js @@ -27,7 +27,7 @@ describe('GovernorCountingFractional', function () { const [owner, proposer, voter1, voter2, voter3, voter4, other] = await ethers.getSigners(); const receiver = await ethers.deployContract('CallReceiverMock'); - const token = await ethers.deployContract(Token, [tokenName, tokenSymbol, version]); + const token = await ethers.deployContract(Token, [tokenName, tokenSymbol, tokenName, version]); const mock = await ethers.deployContract('$GovernorFractionalMock', [ name, // name votingDelay, // initialVotingDelay diff --git a/test/governance/extensions/GovernorERC721.test.js b/test/governance/extensions/GovernorERC721.test.js index 1ae5508d7da..15910b8fe1f 100644 --- a/test/governance/extensions/GovernorERC721.test.js +++ b/test/governance/extensions/GovernorERC721.test.js @@ -29,7 +29,7 @@ describe('GovernorERC721', function () { const [owner, voter1, voter2, voter3, voter4] = await ethers.getSigners(); const receiver = await ethers.deployContract('CallReceiverMock'); - const token = await ethers.deployContract(Token, [tokenName, tokenSymbol, version]); + const token = await ethers.deployContract(Token, [tokenName, tokenSymbol, tokenName, version]); const mock = await ethers.deployContract('$GovernorMock', [ name, // name votingDelay, // initialVotingDelay diff --git a/test/governance/extensions/GovernorPreventLateQuorum.test.js b/test/governance/extensions/GovernorPreventLateQuorum.test.js index aac0e689815..761087aa945 100644 --- a/test/governance/extensions/GovernorPreventLateQuorum.test.js +++ b/test/governance/extensions/GovernorPreventLateQuorum.test.js @@ -28,7 +28,7 @@ describe('GovernorPreventLateQuorum', function () { const [owner, proposer, voter1, voter2, voter3, voter4] = await ethers.getSigners(); const receiver = await ethers.deployContract('CallReceiverMock'); - const token = await ethers.deployContract(Token, [tokenName, tokenSymbol, version]); + const token = await ethers.deployContract(Token, [tokenName, tokenSymbol, tokenName, version]); const mock = await ethers.deployContract('$GovernorPreventLateQuorumMock', [ name, // name votingDelay, // initialVotingDelay diff --git a/test/governance/extensions/GovernorStorage.test.js b/test/governance/extensions/GovernorStorage.test.js index ef56fa53e44..f079405b5a5 100644 --- a/test/governance/extensions/GovernorStorage.test.js +++ b/test/governance/extensions/GovernorStorage.test.js @@ -33,7 +33,7 @@ describe('GovernorStorage', function () { const [deployer, owner, proposer, voter1, voter2, voter3, voter4] = await ethers.getSigners(); const receiver = await ethers.deployContract('CallReceiverMock'); - const token = await ethers.deployContract(Token, [tokenName, tokenSymbol, version]); + const token = await ethers.deployContract(Token, [tokenName, tokenSymbol, tokenName, version]); const timelock = await ethers.deployContract('TimelockController', [delay, [], [], deployer]); const mock = await ethers.deployContract('$GovernorStorageMock', [ name, diff --git a/test/governance/extensions/GovernorTimelockAccess.test.js b/test/governance/extensions/GovernorTimelockAccess.test.js index c3d3b32684e..5eea6478abd 100644 --- a/test/governance/extensions/GovernorTimelockAccess.test.js +++ b/test/governance/extensions/GovernorTimelockAccess.test.js @@ -40,7 +40,7 @@ describe('GovernorTimelockAccess', function () { const manager = await ethers.deployContract('$AccessManager', [admin]); const receiver = await ethers.deployContract('$AccessManagedTarget', [manager]); - const token = await ethers.deployContract(Token, [tokenName, tokenSymbol, version]); + const token = await ethers.deployContract(Token, [tokenName, tokenSymbol, tokenName, version]); const mock = await ethers.deployContract('$GovernorTimelockAccessMock', [ name, votingDelay, diff --git a/test/governance/extensions/GovernorTimelockCompound.test.js b/test/governance/extensions/GovernorTimelockCompound.test.js index 545bf359d30..cd82481d500 100644 --- a/test/governance/extensions/GovernorTimelockCompound.test.js +++ b/test/governance/extensions/GovernorTimelockCompound.test.js @@ -28,7 +28,7 @@ describe('GovernorTimelockCompound', function () { const [deployer, owner, voter1, voter2, voter3, voter4, other] = await ethers.getSigners(); const receiver = await ethers.deployContract('CallReceiverMock'); - const token = await ethers.deployContract(Token, [tokenName, tokenSymbol, version]); + const token = await ethers.deployContract(Token, [tokenName, tokenSymbol, tokenName, version]); const predictGovernor = await deployer .getNonce() .then(nonce => ethers.getCreateAddress({ from: deployer.address, nonce: nonce + 1 })); diff --git a/test/governance/extensions/GovernorTimelockControl.test.js b/test/governance/extensions/GovernorTimelockControl.test.js index c1156a50993..507c7e27832 100644 --- a/test/governance/extensions/GovernorTimelockControl.test.js +++ b/test/governance/extensions/GovernorTimelockControl.test.js @@ -34,7 +34,7 @@ describe('GovernorTimelockControl', function () { const [deployer, owner, voter1, voter2, voter3, voter4, other] = await ethers.getSigners(); const receiver = await ethers.deployContract('CallReceiverMock'); - const token = await ethers.deployContract(Token, [tokenName, tokenSymbol, version]); + const token = await ethers.deployContract(Token, [tokenName, tokenSymbol, tokenName, version]); const timelock = await ethers.deployContract('TimelockController', [delay, [], [], deployer]); const mock = await ethers.deployContract('$GovernorTimelockControlMock', [ name, diff --git a/test/governance/extensions/GovernorVotesQuorumFraction.test.js b/test/governance/extensions/GovernorVotesQuorumFraction.test.js index 368e396f92b..99afd393173 100644 --- a/test/governance/extensions/GovernorVotesQuorumFraction.test.js +++ b/test/governance/extensions/GovernorVotesQuorumFraction.test.js @@ -29,7 +29,7 @@ describe('GovernorVotesQuorumFraction', function () { const receiver = await ethers.deployContract('CallReceiverMock'); - const token = await ethers.deployContract(Token, [tokenName, tokenSymbol, version]); + const token = await ethers.deployContract(Token, [tokenName, tokenSymbol, tokenName, version]); const mock = await ethers.deployContract('$GovernorMock', [name, votingDelay, votingPeriod, 0n, token, ratio]); await owner.sendTransaction({ to: mock, value }); diff --git a/test/governance/extensions/GovernorWithParams.test.js b/test/governance/extensions/GovernorWithParams.test.js index 37e15f5c2dd..db19bc61683 100644 --- a/test/governance/extensions/GovernorWithParams.test.js +++ b/test/governance/extensions/GovernorWithParams.test.js @@ -31,7 +31,7 @@ describe('GovernorWithParams', function () { const [owner, proposer, voter1, voter2, voter3, voter4, other] = await ethers.getSigners(); const receiver = await ethers.deployContract('CallReceiverMock'); - const token = await ethers.deployContract(Token, [tokenName, tokenSymbol, version]); + const token = await ethers.deployContract(Token, [tokenName, tokenSymbol, tokenName, version]); const mock = await ethers.deployContract('$GovernorWithParamsMock', [name, token]); await owner.sendTransaction({ to: mock, value }); diff --git a/test/metatx/ERC2771Context.test.js b/test/metatx/ERC2771Context.test.js index 15da61dad1e..93354d0fc8b 100644 --- a/test/metatx/ERC2771Context.test.js +++ b/test/metatx/ERC2771Context.test.js @@ -11,7 +11,7 @@ const { shouldBehaveLikeRegularContext } = require('../utils/Context.behavior'); async function fixture() { const [sender, other] = await ethers.getSigners(); - const forwarder = await ethers.deployContract('ERC2771Forwarder', []); + const forwarder = await ethers.deployContract('ERC2771Forwarder', ['ERC2771Forwarder']); const forwarderAsSigner = await impersonate(forwarder.target); const context = await ethers.deployContract('ERC2771ContextMock', [forwarder]); const domain = await getDomain(forwarder); From 1c1186af1c547277cb06797a4ec631658b143028 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Tue, 10 Dec 2024 11:47:32 -0600 Subject: [PATCH 73/84] Allow pass in custom arguments to `shouldSupportInterfaces` test helper (#5350) Co-authored-by: Hadrien Croubois --- test/helpers/iterate.js | 9 +++++++-- .../introspection/SupportsInterface.behavior.js | 14 +++++++++----- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/test/helpers/iterate.js b/test/helpers/iterate.js index c7403d52384..8c8e9649dea 100644 --- a/test/helpers/iterate.js +++ b/test/helpers/iterate.js @@ -30,7 +30,12 @@ module.exports = { // ================================================ Object helpers ================================================= - // Create a new object by mapping the values through a function, keeping the keys + // Create a new object by mapping the values through a function, keeping the keys. Second function can be used to pre-filter entries // Example: mapValues({a:1,b:2,c:3}, x => x**2) → {a:1,b:4,c:9} - mapValues: (obj, fn) => Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, fn(v)])), + mapValues: (obj, fn, fn2 = () => true) => + Object.fromEntries( + Object.entries(obj) + .filter(fn2) + .map(([k, v]) => [k, fn(v)]), + ), }; diff --git a/test/utils/introspection/SupportsInterface.behavior.js b/test/utils/introspection/SupportsInterface.behavior.js index 8a7bc4b5e2b..78f6ddba521 100644 --- a/test/utils/introspection/SupportsInterface.behavior.js +++ b/test/utils/introspection/SupportsInterface.behavior.js @@ -92,8 +92,10 @@ const SIGNATURES = { const INTERFACE_IDS = mapValues(SIGNATURES, interfaceId); -function shouldSupportInterfaces(interfaces = []) { +function shouldSupportInterfaces(interfaces = [], signatures = SIGNATURES) { interfaces.unshift('ERC165'); + signatures.ERC165 = SIGNATURES.ERC165; + const interfaceIds = mapValues(signatures, interfaceId, ([name]) => interfaces.includes(name)); describe('ERC165', function () { beforeEach(function () { @@ -103,14 +105,14 @@ function shouldSupportInterfaces(interfaces = []) { describe('when the interfaceId is supported', function () { it('uses less than 30k gas', async function () { for (const k of interfaces) { - const interfaceId = INTERFACE_IDS[k] ?? k; + const interfaceId = interfaceIds[k] ?? k; expect(await this.contractUnderTest.supportsInterface.estimateGas(interfaceId)).to.lte(30_000n); } }); it('returns true', async function () { for (const k of interfaces) { - const interfaceId = INTERFACE_IDS[k] ?? k; + const interfaceId = interfaceIds[k] ?? k; expect(await this.contractUnderTest.supportsInterface(interfaceId), `does not support ${k}`).to.be.true; } }); @@ -129,10 +131,10 @@ function shouldSupportInterfaces(interfaces = []) { it('all interface functions are in ABI', async function () { for (const k of interfaces) { // skip interfaces for which we don't have a function list - if (SIGNATURES[k] === undefined) continue; + if (signatures[k] === undefined) continue; // Check the presence of each function in the contract's interface - for (const fnSig of SIGNATURES[k]) { + for (const fnSig of signatures[k]) { expect(this.contractUnderTest.interface.hasFunction(fnSig), `did not find ${fnSig}`).to.be.true; } } @@ -141,5 +143,7 @@ function shouldSupportInterfaces(interfaces = []) { } module.exports = { + SIGNATURES, + INTERFACE_IDS, shouldSupportInterfaces, }; From 6dacc68c46483e5c83aa3c79efeee9b3ccf24a39 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Tue, 10 Dec 2024 21:29:22 +0100 Subject: [PATCH 74/84] Refactor shouldSupportInterfaces helper (#5358) --- test/utils/introspection/SupportsInterface.behavior.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/utils/introspection/SupportsInterface.behavior.js b/test/utils/introspection/SupportsInterface.behavior.js index 78f6ddba521..ae7c5ab5d00 100644 --- a/test/utils/introspection/SupportsInterface.behavior.js +++ b/test/utils/introspection/SupportsInterface.behavior.js @@ -93,6 +93,12 @@ const SIGNATURES = { const INTERFACE_IDS = mapValues(SIGNATURES, interfaceId); function shouldSupportInterfaces(interfaces = [], signatures = SIGNATURES) { + // case where only signatures are provided + if (!Array.isArray(interfaces)) { + interfaces = Object.keys(interfaces); + signatures = interfaces; + } + interfaces.unshift('ERC165'); signatures.ERC165 = SIGNATURES.ERC165; const interfaceIds = mapValues(signatures, interfaceId, ([name]) => interfaces.includes(name)); From ff3134197fbc00183f782a7dc085302b7068c6c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Tue, 10 Dec 2024 15:31:26 -0600 Subject: [PATCH 75/84] Amend shouldSupportInterfaces refactor (#5359) --- test/utils/introspection/SupportsInterface.behavior.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/utils/introspection/SupportsInterface.behavior.js b/test/utils/introspection/SupportsInterface.behavior.js index ae7c5ab5d00..51948279c50 100644 --- a/test/utils/introspection/SupportsInterface.behavior.js +++ b/test/utils/introspection/SupportsInterface.behavior.js @@ -95,8 +95,8 @@ const INTERFACE_IDS = mapValues(SIGNATURES, interfaceId); function shouldSupportInterfaces(interfaces = [], signatures = SIGNATURES) { // case where only signatures are provided if (!Array.isArray(interfaces)) { - interfaces = Object.keys(interfaces); signatures = interfaces; + interfaces = Object.keys(interfaces); } interfaces.unshift('ERC165'); From a39556f4751fcfc7c01241553f32ad60eb2438ca Mon Sep 17 00:00:00 2001 From: Woolfgm <160153877+Dahka2321@users.noreply.github.com> Date: Wed, 11 Dec 2024 17:09:11 +0100 Subject: [PATCH 76/84] Fix GovernorTimelockCompound and TESTING.md grammar (#5356) --- contracts/governance/extensions/GovernorTimelockCompound.sol | 2 +- test/TESTING.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/governance/extensions/GovernorTimelockCompound.sol b/contracts/governance/extensions/GovernorTimelockCompound.sol index 309f9a4fa76..f98edc8362e 100644 --- a/contracts/governance/extensions/GovernorTimelockCompound.sol +++ b/contracts/governance/extensions/GovernorTimelockCompound.sol @@ -10,7 +10,7 @@ import {SafeCast} from "../../utils/math/SafeCast.sol"; /** * @dev Extension of {Governor} that binds the execution process to a Compound Timelock. This adds a delay, enforced by - * the external timelock to all successful proposal (in addition to the voting duration). The {Governor} needs to be + * the external timelock to all successful proposals (in addition to the voting duration). The {Governor} needs to be * the admin of the timelock for any operation to be performed. A public, unrestricted, * {GovernorTimelockCompound-__acceptAdmin} is available to accept ownership of the timelock. * diff --git a/test/TESTING.md b/test/TESTING.md index a5ee9323ff4..321c7e592d6 100644 --- a/test/TESTING.md +++ b/test/TESTING.md @@ -1,3 +1,3 @@ ## Testing -Unit test are critical to OpenZeppelin Contracts. They help ensure code quality and mitigate against security vulnerabilities. The directory structure within the `/test` directory corresponds to the `/contracts` directory. +Unit tests are critical to OpenZeppelin Contracts. They help ensure code quality and mitigate against security vulnerabilities. The directory structure within the `/test` directory corresponds to the `/contracts` directory. From 2875a0f78236a1b8f414cd6dd8726fc44997d34a Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Wed, 11 Dec 2024 17:32:36 +0100 Subject: [PATCH 77/84] Add v5.2 audit report (#5341) --- audits/2024-12-v5.2.pdf | Bin 0 -> 242253 bytes audits/README.md | 17 +++++++++-------- 2 files changed, 9 insertions(+), 8 deletions(-) create mode 100644 audits/2024-12-v5.2.pdf diff --git a/audits/2024-12-v5.2.pdf b/audits/2024-12-v5.2.pdf new file mode 100644 index 0000000000000000000000000000000000000000..ef138f7ec61f268020d139c587118e8b476f4cda GIT binary patch literal 242253 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 #Y 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 literal 0 HcmV?d00001 diff --git a/audits/README.md b/audits/README.md index 34bda2af305..4e8c94653cd 100644 --- a/audits/README.md +++ b/audits/README.md @@ -1,13 +1,14 @@ # Audits -| Date | Version | Commit | Auditor | Scope | Links | -| ------------ | ------- | --------- | ------------ | -------------------- | ----------------------------------------------------------- | -| October 2024 | v5.1.0 | TBD | OpenZeppelin | v5.1 Changes | [🔗](./2024-10-v5.1.pdf) | -| October 2023 | v5.0.0 | `b5a3e69` | OpenZeppelin | v5.0 Changes | [🔗](./2023-10-v5.0.pdf) | -| May 2023 | v4.9.0 | `91df66c` | OpenZeppelin | v4.9 Changes | [🔗](./2023-05-v4.9.pdf) | -| October 2022 | v4.8.0 | `14f98db` | OpenZeppelin | ERC4626, Checkpoints | [🔗](./2022-10-ERC4626.pdf) [🔗](./2022-10-Checkpoints.pdf) | -| October 2018 | v2.0.0 | `dac5bcc` | LevelK | Everything | [🔗](./2018-10.pdf) | -| March 2017 | v1.0.4 | `9c5975a` | New Alchemy | Everything | [🔗](./2017-03.md) | +| Date | Version | Commit | Auditor | Scope | Links | +| ------------- | ------- | -------------------------------------------------------------------------------- | ------------ | -------------------- | ----------------------------------------------------------- | +| December 2024 | v5.2.0 | [`98d28f9`](https://github.com/openzeppelin/openzeppelin-contracts/tree/98d28f9) | OpenZeppelin | v5.2 Changes | [🔗](./2024-12-v5.2.pdf) | +| October 2024 | v5.1.0 | [`aba9ff6`](https://github.com/openzeppelin/openzeppelin-contracts/tree/aba9ff6) | OpenZeppelin | v5.1 Changes | [🔗](./2024-10-v5.1.pdf) | +| October 2023 | v5.0.0 | [`b5a3e69`](https://github.com/openzeppelin/openzeppelin-contracts/tree/b5a3e69) | OpenZeppelin | v5.0 Changes | [🔗](./2023-10-v5.0.pdf) | +| May 2023 | v4.9.0 | [`91df66c`](https://github.com/openzeppelin/openzeppelin-contracts/tree/91df66c) | OpenZeppelin | v4.9 Changes | [🔗](./2023-05-v4.9.pdf) | +| October 2022 | v4.8.0 | [`14f98db`](https://github.com/openzeppelin/openzeppelin-contracts/tree/14f98db) | OpenZeppelin | ERC4626, Checkpoints | [🔗](./2022-10-ERC4626.pdf) [🔗](./2022-10-Checkpoints.pdf) | +| October 2018 | v2.0.0 | [`dac5bcc`](https://github.com/openzeppelin/openzeppelin-contracts/tree/dac5bcc) | LevelK | Everything | [🔗](./2018-10.pdf) | +| March 2017 | v1.0.4 | [`9c5975a`](https://github.com/openzeppelin/openzeppelin-contracts/tree/9c5975a) | New Alchemy | Everything | [🔗](./2017-03.md) | # Formal Verification From 8939cb783541d41fb21185865243754432e078d5 Mon Sep 17 00:00:00 2001 From: lfg2 Date: Thu, 12 Dec 2024 19:34:22 +0800 Subject: [PATCH 78/84] Documentation: Fix README.adoc (#5361) --- contracts/utils/README.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/utils/README.adoc b/contracts/utils/README.adoc index 432b806e3c4..8f08a86987f 100644 --- a/contracts/utils/README.adoc +++ b/contracts/utils/README.adoc @@ -42,7 +42,7 @@ Miscellaneous contracts and libraries containing utility functions you can use t * {Context}: A utility for abstracting the sender and calldata in the current execution context. * {Packing}: A library for packing and unpacking multiple values into bytes32 * {Panic}: A library to revert with https://docs.soliditylang.org/en/v0.8.20/control-structures.html#panic-via-assert-and-error-via-require[Solidity panic codes]. - * {Comparators}: A library that contains comparator functions to use with with the {Heap} library. + * {Comparators}: A library that contains comparator functions to use with the {Heap} library. * {CAIP2}, {CAIP10}: Libraries for formatting and parsing CAIP-2 and CAIP-10 identifiers. [NOTE] From ad906fe39b643dd1bfbadc8480a631d223a75d6a Mon Sep 17 00:00:00 2001 From: Dmitry <98899785+mdqst@users.noreply.github.com> Date: Thu, 12 Dec 2024 14:35:10 +0300 Subject: [PATCH 79/84] fix typo Update inheritance-ordering.js (#5354) --- scripts/checks/inheritance-ordering.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/checks/inheritance-ordering.js b/scripts/checks/inheritance-ordering.js index 4ed2deec454..fbeac9ea7c4 100755 --- a/scripts/checks/inheritance-ordering.js +++ b/scripts/checks/inheritance-ordering.js @@ -31,7 +31,7 @@ for (const artifact of artifacts) { } /// graphlib.alg.findCycles will not find minimal cycles. - /// We are only interested int cycles of lengths 2 (needs proof) + /// We are only interested in cycles of lengths 2 (needs proof) graph.nodes().forEach((x, i, nodes) => nodes .slice(i + 1) From 5df10703cd99904375f32b9c63cd6347ee4bfd1a Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 12 Dec 2024 16:09:20 +0100 Subject: [PATCH 80/84] Vendor entrypoint bytecode (#5362) --- foundry.toml | 1 + test/account/utils/draft-ERC4337Utils.test.js | 14 ++++++-- test/bin/EntryPoint070.abi | 1 + test/bin/EntryPoint070.bytecode | Bin 0 -> 16035 bytes test/bin/SenderCreator070.abi | 1 + test/bin/SenderCreator070.bytecode | Bin 0 -> 451 bytes test/helpers/erc4337-entrypoint.js | 31 ++++++++++++++++++ 7 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 test/bin/EntryPoint070.abi create mode 100644 test/bin/EntryPoint070.bytecode create mode 100644 test/bin/SenderCreator070.abi create mode 100644 test/bin/SenderCreator070.bytecode create mode 100644 test/helpers/erc4337-entrypoint.js diff --git a/foundry.toml b/foundry.toml index 3f60b7cbbf1..78dd0781224 100644 --- a/foundry.toml +++ b/foundry.toml @@ -8,6 +8,7 @@ out = 'out' libs = ['node_modules', 'lib'] test = 'test' cache_path = 'cache_forge' +fs_permissions = [{ access = "read", path = "./test/bin" }] [fuzz] runs = 5000 diff --git a/test/account/utils/draft-ERC4337Utils.test.js b/test/account/utils/draft-ERC4337Utils.test.js index ab2500b5382..7c292910dfb 100644 --- a/test/account/utils/draft-ERC4337Utils.test.js +++ b/test/account/utils/draft-ERC4337Utils.test.js @@ -3,11 +3,13 @@ const { expect } = require('chai'); const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); const { packValidationData, UserOperation } = require('../../helpers/erc4337'); +const { deployEntrypoint } = require('../../helpers/erc4337-entrypoint'); const { MAX_UINT48 } = require('../../helpers/constants'); const ADDRESS_ONE = '0x0000000000000000000000000000000000000001'; const fixture = async () => { - const [authorizer, sender, entrypoint, factory, paymaster] = await ethers.getSigners(); + const { entrypoint } = await deployEntrypoint(); + const [authorizer, sender, factory, paymaster] = await ethers.getSigners(); const utils = await ethers.deployContract('$ERC4337Utils'); const SIG_VALIDATION_SUCCESS = await utils.$SIG_VALIDATION_SUCCESS(); const SIG_VALIDATION_FAILED = await utils.$SIG_VALIDATION_FAILED(); @@ -167,11 +169,19 @@ describe('ERC4337Utils', function () { describe('hash', function () { it('returns the operation hash with specified entrypoint and chainId', async function () { const userOp = new UserOperation({ sender: this.sender, nonce: 1 }); - const chainId = 0xdeadbeef; + const chainId = await ethers.provider.getNetwork().then(({ chainId }) => chainId); + const otherChainId = 0xdeadbeef; + // check that helper matches entrypoint logic + expect(this.entrypoint.getUserOpHash(userOp.packed)).to.eventually.equal(userOp.hash(this.entrypoint, chainId)); + + // check library against helper expect(this.utils.$hash(userOp.packed, this.entrypoint, chainId)).to.eventually.equal( userOp.hash(this.entrypoint, chainId), ); + expect(this.utils.$hash(userOp.packed, this.entrypoint, otherChainId)).to.eventually.equal( + userOp.hash(this.entrypoint, otherChainId), + ); }); }); diff --git a/test/bin/EntryPoint070.abi b/test/bin/EntryPoint070.abi new file mode 100644 index 00000000000..3f3b1d6e5ff --- /dev/null +++ b/test/bin/EntryPoint070.abi @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"bool","name":"success","type":"bool"},{"internalType":"bytes","name":"ret","type":"bytes"}],"name":"DelegateAndRevert","type":"error"},{"inputs":[{"internalType":"uint256","name":"opIndex","type":"uint256"},{"internalType":"string","name":"reason","type":"string"}],"name":"FailedOp","type":"error"},{"inputs":[{"internalType":"uint256","name":"opIndex","type":"uint256"},{"internalType":"string","name":"reason","type":"string"},{"internalType":"bytes","name":"inner","type":"bytes"}],"name":"FailedOpWithRevert","type":"error"},{"inputs":[{"internalType":"bytes","name":"returnData","type":"bytes"}],"name":"PostOpReverted","type":"error"},{"inputs":[],"name":"ReentrancyGuardReentrantCall","type":"error"},{"inputs":[{"internalType":"address","name":"sender","type":"address"}],"name":"SenderAddressResult","type":"error"},{"inputs":[{"internalType":"address","name":"aggregator","type":"address"}],"name":"SignatureValidationFailed","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"userOpHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"address","name":"factory","type":"address"},{"indexed":false,"internalType":"address","name":"paymaster","type":"address"}],"name":"AccountDeployed","type":"event"},{"anonymous":false,"inputs":[],"name":"BeforeExecution","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"totalDeposit","type":"uint256"}],"name":"Deposited","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"userOpHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"nonce","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"revertReason","type":"bytes"}],"name":"PostOpRevertReason","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"aggregator","type":"address"}],"name":"SignatureAggregatorChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"totalStaked","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"unstakeDelaySec","type":"uint256"}],"name":"StakeLocked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"withdrawTime","type":"uint256"}],"name":"StakeUnlocked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"address","name":"withdrawAddress","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"StakeWithdrawn","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"userOpHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"paymaster","type":"address"},{"indexed":false,"internalType":"uint256","name":"nonce","type":"uint256"},{"indexed":false,"internalType":"bool","name":"success","type":"bool"},{"indexed":false,"internalType":"uint256","name":"actualGasCost","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"actualGasUsed","type":"uint256"}],"name":"UserOperationEvent","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"userOpHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"nonce","type":"uint256"}],"name":"UserOperationPrefundTooLow","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"userOpHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"nonce","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"revertReason","type":"bytes"}],"name":"UserOperationRevertReason","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"address","name":"withdrawAddress","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Withdrawn","type":"event"},{"inputs":[{"internalType":"uint32","name":"unstakeDelaySec","type":"uint32"}],"name":"addStake","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"target","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"delegateAndRevert","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"depositTo","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"deposits","outputs":[{"internalType":"uint256","name":"deposit","type":"uint256"},{"internalType":"bool","name":"staked","type":"bool"},{"internalType":"uint112","name":"stake","type":"uint112"},{"internalType":"uint32","name":"unstakeDelaySec","type":"uint32"},{"internalType":"uint48","name":"withdrawTime","type":"uint48"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"getDepositInfo","outputs":[{"components":[{"internalType":"uint256","name":"deposit","type":"uint256"},{"internalType":"bool","name":"staked","type":"bool"},{"internalType":"uint112","name":"stake","type":"uint112"},{"internalType":"uint32","name":"unstakeDelaySec","type":"uint32"},{"internalType":"uint48","name":"withdrawTime","type":"uint48"}],"internalType":"struct IStakeManager.DepositInfo","name":"info","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint192","name":"key","type":"uint192"}],"name":"getNonce","outputs":[{"internalType":"uint256","name":"nonce","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"initCode","type":"bytes"}],"name":"getSenderAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"bytes","name":"initCode","type":"bytes"},{"internalType":"bytes","name":"callData","type":"bytes"},{"internalType":"bytes32","name":"accountGasLimits","type":"bytes32"},{"internalType":"uint256","name":"preVerificationGas","type":"uint256"},{"internalType":"bytes32","name":"gasFees","type":"bytes32"},{"internalType":"bytes","name":"paymasterAndData","type":"bytes"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct PackedUserOperation","name":"userOp","type":"tuple"}],"name":"getUserOpHash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"components":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"bytes","name":"initCode","type":"bytes"},{"internalType":"bytes","name":"callData","type":"bytes"},{"internalType":"bytes32","name":"accountGasLimits","type":"bytes32"},{"internalType":"uint256","name":"preVerificationGas","type":"uint256"},{"internalType":"bytes32","name":"gasFees","type":"bytes32"},{"internalType":"bytes","name":"paymasterAndData","type":"bytes"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct PackedUserOperation[]","name":"userOps","type":"tuple[]"},{"internalType":"contract IAggregator","name":"aggregator","type":"address"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct IEntryPoint.UserOpsPerAggregator[]","name":"opsPerAggregator","type":"tuple[]"},{"internalType":"address payable","name":"beneficiary","type":"address"}],"name":"handleAggregatedOps","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"bytes","name":"initCode","type":"bytes"},{"internalType":"bytes","name":"callData","type":"bytes"},{"internalType":"bytes32","name":"accountGasLimits","type":"bytes32"},{"internalType":"uint256","name":"preVerificationGas","type":"uint256"},{"internalType":"bytes32","name":"gasFees","type":"bytes32"},{"internalType":"bytes","name":"paymasterAndData","type":"bytes"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct PackedUserOperation[]","name":"ops","type":"tuple[]"},{"internalType":"address payable","name":"beneficiary","type":"address"}],"name":"handleOps","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint192","name":"key","type":"uint192"}],"name":"incrementNonce","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"callData","type":"bytes"},{"components":[{"components":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"verificationGasLimit","type":"uint256"},{"internalType":"uint256","name":"callGasLimit","type":"uint256"},{"internalType":"uint256","name":"paymasterVerificationGasLimit","type":"uint256"},{"internalType":"uint256","name":"paymasterPostOpGasLimit","type":"uint256"},{"internalType":"uint256","name":"preVerificationGas","type":"uint256"},{"internalType":"address","name":"paymaster","type":"address"},{"internalType":"uint256","name":"maxFeePerGas","type":"uint256"},{"internalType":"uint256","name":"maxPriorityFeePerGas","type":"uint256"}],"internalType":"struct EntryPoint.MemoryUserOp","name":"mUserOp","type":"tuple"},{"internalType":"bytes32","name":"userOpHash","type":"bytes32"},{"internalType":"uint256","name":"prefund","type":"uint256"},{"internalType":"uint256","name":"contextOffset","type":"uint256"},{"internalType":"uint256","name":"preOpGas","type":"uint256"}],"internalType":"struct EntryPoint.UserOpInfo","name":"opInfo","type":"tuple"},{"internalType":"bytes","name":"context","type":"bytes"}],"name":"innerHandleOp","outputs":[{"internalType":"uint256","name":"actualGasCost","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint192","name":"","type":"uint192"}],"name":"nonceSequenceNumber","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"unlockStake","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address payable","name":"withdrawAddress","type":"address"}],"name":"withdrawStake","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address payable","name":"withdrawAddress","type":"address"},{"internalType":"uint256","name":"withdrawAmount","type":"uint256"}],"name":"withdrawTo","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}] \ No newline at end of file diff --git a/test/bin/EntryPoint070.bytecode b/test/bin/EntryPoint070.bytecode new file mode 100644 index 0000000000000000000000000000000000000000..fce261af5dc93a09c7ab25cd89463d1b23c5a223 GIT binary patch literal 16035 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 literal 0 HcmV?d00001 diff --git a/test/bin/SenderCreator070.abi b/test/bin/SenderCreator070.abi new file mode 100644 index 00000000000..0a1f0e4fbac --- /dev/null +++ b/test/bin/SenderCreator070.abi @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"bytes","name":"initCode","type":"bytes"}],"name":"createSender","outputs":[{"internalType":"address","name":"sender","type":"address"}],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/test/bin/SenderCreator070.bytecode b/test/bin/SenderCreator070.bytecode new file mode 100644 index 0000000000000000000000000000000000000000..8344c20281957f0aff5dd71a3650f5553f520704 GIT binary patch literal 451 zcmYdjNMJD&5KUy@4^Lod_!|u*OcNf+B!}}!nTaGas6s?d5*fR~6BO$I;|G7t7=^$p zSxhIU!<0^FY!OUkYzsF_P;O-u5(P1v6Id8cn;L<_O+aClrgla~FuNf^1WXgM=S?Hy zqy~_wq2Yv-K1i@{OyF*16k`-?W}ML6Bru^#P#mOjQcHqLOca_W~?3eV~_71&aW^K+7&892lli~-q#;|Bl$ literal 0 HcmV?d00001 diff --git a/test/helpers/erc4337-entrypoint.js b/test/helpers/erc4337-entrypoint.js new file mode 100644 index 00000000000..aba49f4c458 --- /dev/null +++ b/test/helpers/erc4337-entrypoint.js @@ -0,0 +1,31 @@ +const { ethers } = require('hardhat'); +const { setCode } = require('@nomicfoundation/hardhat-network-helpers'); +const fs = require('fs'); +const path = require('path'); + +const INSTANCES = { + entrypoint: { + address: '0x0000000071727De22E5E9d8BAf0edAc6f37da032', + abi: JSON.parse(fs.readFileSync(path.resolve(__dirname, '../bin/EntryPoint070.abi'), 'utf-8')), + bytecode: fs.readFileSync(path.resolve(__dirname, '../bin/EntryPoint070.bytecode'), 'hex'), + }, + sendercreator: { + address: '0xEFC2c1444eBCC4Db75e7613d20C6a62fF67A167C', + abi: JSON.parse(fs.readFileSync(path.resolve(__dirname, '../bin/SenderCreator070.abi'), 'utf-8')), + bytecode: fs.readFileSync(path.resolve(__dirname, '../bin/SenderCreator070.bytecode'), 'hex'), + }, +}; + +function deployEntrypoint() { + return Promise.all( + Object.entries(INSTANCES).map(([name, { address, abi, bytecode }]) => + setCode(address, '0x' + bytecode.replace(/0x/, '')) + .then(() => ethers.getContractAt(abi, address)) + .then(instance => ({ [name]: instance })), + ), + ).then(namedInstances => Object.assign(...namedInstances)); +} + +module.exports = { + deployEntrypoint, +}; From 77d4a73c87ba0421dd2f05cf2137f5e6de239fa3 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 12 Dec 2024 21:53:52 +0100 Subject: [PATCH 81/84] Add checks to ERC7579Utils.decodeBatch (#5353) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernesto García Co-authored-by: Francisco Giordano --- .../account/utils/draft-ERC7579Utils.sol | 53 ++- test/account/utils/draft-ERC7579Utils.t.sol | 421 ++++++++++++++++++ test/account/utils/draft-ERC7579Utils.test.js | 28 +- 3 files changed, 477 insertions(+), 25 deletions(-) create mode 100644 test/account/utils/draft-ERC7579Utils.t.sol diff --git a/contracts/account/utils/draft-ERC7579Utils.sol b/contracts/account/utils/draft-ERC7579Utils.sol index dab18f26bf0..c672fb279fe 100644 --- a/contracts/account/utils/draft-ERC7579Utils.sol +++ b/contracts/account/utils/draft-ERC7579Utils.sol @@ -61,10 +61,13 @@ library ERC7579Utils { /// @dev The module type is not supported. error ERC7579UnsupportedModuleType(uint256 moduleTypeId); + /// @dev Input calldata not properly formatted and possibly malicious. + error ERC7579DecodingError(); + /// @dev Executes a single call. function execSingle( - ExecType execType, - bytes calldata executionCalldata + bytes calldata executionCalldata, + ExecType execType ) internal returns (bytes[] memory returnData) { (address target, uint256 value, bytes calldata callData) = decodeSingle(executionCalldata); returnData = new bytes[](1); @@ -73,8 +76,8 @@ library ERC7579Utils { /// @dev Executes a batch of calls. function execBatch( - ExecType execType, - bytes calldata executionCalldata + bytes calldata executionCalldata, + ExecType execType ) internal returns (bytes[] memory returnData) { Execution[] calldata executionBatch = decodeBatch(executionCalldata); returnData = new bytes[](executionBatch.length); @@ -91,8 +94,8 @@ library ERC7579Utils { /// @dev Executes a delegate call. function execDelegateCall( - ExecType execType, - bytes calldata executionCalldata + bytes calldata executionCalldata, + ExecType execType ) internal returns (bytes[] memory returnData) { (address target, bytes calldata callData) = decodeDelegate(executionCalldata); returnData = new bytes[](1); @@ -169,12 +172,40 @@ library ERC7579Utils { } /// @dev Decodes a batch of executions. See {encodeBatch}. + /// + /// NOTE: This function runs some checks and will throw a {ERC7579DecodingError} if the input is not properly formatted. function decodeBatch(bytes calldata executionCalldata) internal pure returns (Execution[] calldata executionBatch) { - assembly ("memory-safe") { - let ptr := add(executionCalldata.offset, calldataload(executionCalldata.offset)) - // Extract the ERC7579 Executions - executionBatch.offset := add(ptr, 32) - executionBatch.length := calldataload(ptr) + unchecked { + uint256 bufferLength = executionCalldata.length; + + // Check executionCalldata is not empty. + if (bufferLength < 32) revert ERC7579DecodingError(); + + // Get the offset of the array (pointer to the array length). + uint256 arrayLengthPointer = uint256(bytes32(executionCalldata[0:32])); + + // The array length (at arrayLengthPointer) should be 32 bytes long. We check that this is within the + // buffer bounds. Since we know bufferLength is at least 32, we can subtract with no overflow risk. + if (arrayLengthPointer > bufferLength - 32) revert ERC7579DecodingError(); + + // Get the array length. arrayLengthPointer + 32 is bounded by bufferLength so it does not overflow. + uint256 arrayLength = uint256(bytes32(executionCalldata[arrayLengthPointer:arrayLengthPointer + 32])); + + // Check that the buffer is long enough to store the array elements as "offset pointer": + // - each element of the array is an "offset pointer" to the data. + // - each "offset pointer" (to an array element) takes 32 bytes. + // - validity of the calldata at that location is checked when the array element is accessed, so we only + // need to check that the buffer is large enough to hold the pointers. + // + // Since we know bufferLength is at least arrayLengthPointer + 32, we can subtract with no overflow risk. + // Solidity limits length of such arrays to 2**64-1, this guarantees `arrayLength * 32` does not overflow. + if (arrayLength > type(uint64).max || bufferLength - arrayLengthPointer - 32 < arrayLength * 32) + revert ERC7579DecodingError(); + + assembly ("memory-safe") { + executionBatch.offset := add(add(executionCalldata.offset, arrayLengthPointer), 32) + executionBatch.length := arrayLength + } } } diff --git a/test/account/utils/draft-ERC7579Utils.t.sol b/test/account/utils/draft-ERC7579Utils.t.sol new file mode 100644 index 00000000000..fdd4edf5958 --- /dev/null +++ b/test/account/utils/draft-ERC7579Utils.t.sol @@ -0,0 +1,421 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + +// Parts of this test file are adapted from Adam Egyed (@adamegyed) proof of concept available at: +// https://github.com/adamegyed/erc7579-execute-vulnerability/tree/4589a30ff139e143d6c57183ac62b5c029217a90 +// +// solhint-disable no-console + +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; +import {PackedUserOperation, IAccount, IEntryPoint} from "@openzeppelin/contracts/interfaces/draft-IERC4337.sol"; +import {ERC4337Utils} from "@openzeppelin/contracts/account/utils/draft-ERC4337Utils.sol"; +import {ERC7579Utils, Mode, CallType, ExecType, ModeSelector, ModePayload, Execution} from "@openzeppelin/contracts/account/utils/draft-ERC7579Utils.sol"; +import {Test, Vm, console} from "forge-std/Test.sol"; + +contract SampleAccount is IAccount, Ownable { + using ECDSA for *; + using MessageHashUtils for *; + using ERC4337Utils for *; + using ERC7579Utils for *; + + IEntryPoint internal constant ENTRY_POINT = IEntryPoint(payable(0x0000000071727De22E5E9d8BAf0edAc6f37da032)); + + event Log(bool duringValidation, Execution[] calls); + + error UnsupportedCallType(CallType callType); + + constructor(address initialOwner) Ownable(initialOwner) {} + + function validateUserOp( + PackedUserOperation calldata userOp, + bytes32 userOpHash, + uint256 missingAccountFunds + ) external override returns (uint256 validationData) { + require(msg.sender == address(ENTRY_POINT), "only from EP"); + // Check signature + if (userOpHash.toEthSignedMessageHash().recover(userOp.signature) != owner()) { + revert OwnableUnauthorizedAccount(_msgSender()); + } + + // If this is an execute call with a batch operation, log the call details from the calldata + if (bytes4(userOp.callData[0x00:0x04]) == this.execute.selector) { + (CallType callType, , , ) = Mode.wrap(bytes32(userOp.callData[0x04:0x24])).decodeMode(); + + if (callType == ERC7579Utils.CALLTYPE_BATCH) { + // Remove the selector + bytes calldata params = userOp.callData[0x04:]; + + // Use the same vulnerable assignment technique here, but assert afterwards that the checks aren't + // broken here by comparing to the result of `abi.decode(...)`. + bytes calldata executionCalldata; + assembly ("memory-safe") { + let dataptr := add(params.offset, calldataload(add(params.offset, 0x20))) + executionCalldata.offset := add(dataptr, 32) + executionCalldata.length := calldataload(dataptr) + } + // Check that this decoding step is done correctly. + (, bytes memory executionCalldataMemory) = abi.decode(params, (bytes32, bytes)); + + require( + keccak256(executionCalldata) == keccak256(executionCalldataMemory), + "decoding during validation failed" + ); + // Now, we know that we have `bytes calldata executionCalldata` as would be decoded by the solidity + // builtin decoder for the `execute` function. + + // This is where the vulnerability from ExecutionLib results in a different result between validation + // andexecution. + + emit Log(true, executionCalldata.decodeBatch()); + } + } + + if (missingAccountFunds > 0) { + (bool success, ) = payable(msg.sender).call{value: missingAccountFunds}(""); + success; // Silence warning. The entrypoint should validate the result. + } + + return ERC4337Utils.SIG_VALIDATION_SUCCESS; + } + + function execute(Mode mode, bytes calldata executionCalldata) external payable { + require(msg.sender == address(this) || msg.sender == address(ENTRY_POINT), "not auth"); + + (CallType callType, ExecType execType, , ) = mode.decodeMode(); + + // check if calltype is batch or single + if (callType == ERC7579Utils.CALLTYPE_SINGLE) { + executionCalldata.execSingle(execType); + } else if (callType == ERC7579Utils.CALLTYPE_BATCH) { + executionCalldata.execBatch(execType); + + emit Log(false, executionCalldata.decodeBatch()); + } else if (callType == ERC7579Utils.CALLTYPE_DELEGATECALL) { + executionCalldata.execDelegateCall(execType); + } else { + revert UnsupportedCallType(callType); + } + } +} + +contract ERC7579UtilsTest is Test { + using MessageHashUtils for *; + using ERC4337Utils for *; + using ERC7579Utils for *; + + IEntryPoint private constant ENTRYPOINT = IEntryPoint(payable(0x0000000071727De22E5E9d8BAf0edAc6f37da032)); + address private _owner; + uint256 private _ownerKey; + address private _account; + address private _beneficiary; + address private _recipient1; + address private _recipient2; + + constructor() { + vm.etch(0x0000000071727De22E5E9d8BAf0edAc6f37da032, vm.readFileBinary("test/bin/EntryPoint070.bytecode")); + vm.etch(0xEFC2c1444eBCC4Db75e7613d20C6a62fF67A167C, vm.readFileBinary("test/bin/SenderCreator070.bytecode")); + + // signing key + (_owner, _ownerKey) = makeAddrAndKey("owner"); + + // ERC-4337 account + _account = address(new SampleAccount(_owner)); + vm.deal(_account, 1 ether); + + // other + _beneficiary = makeAddr("beneficiary"); + _recipient1 = makeAddr("recipient1"); + _recipient2 = makeAddr("recipient2"); + } + + function testExecuteBatchDecodeCorrectly() public { + Execution[] memory calls = new Execution[](2); + calls[0] = Execution({target: _recipient1, value: 1 wei, callData: ""}); + calls[1] = Execution({target: _recipient2, value: 1 wei, callData: ""}); + + PackedUserOperation[] memory userOps = new PackedUserOperation[](1); + userOps[0] = PackedUserOperation({ + sender: _account, + nonce: 0, + initCode: "", + callData: abi.encodeCall( + SampleAccount.execute, + ( + ERC7579Utils.encodeMode( + ERC7579Utils.CALLTYPE_BATCH, + ERC7579Utils.EXECTYPE_DEFAULT, + ModeSelector.wrap(0x00), + ModePayload.wrap(0x00) + ), + ERC7579Utils.encodeBatch(calls) + ) + ), + accountGasLimits: _packGas(500_000, 500_000), + preVerificationGas: 0, + gasFees: _packGas(1, 1), + paymasterAndData: "", + signature: "" + }); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign( + _ownerKey, + this.hashUserOperation(userOps[0]).toEthSignedMessageHash() + ); + userOps[0].signature = abi.encodePacked(r, s, v); + + vm.recordLogs(); + ENTRYPOINT.handleOps(userOps, payable(_beneficiary)); + + assertEq(_recipient1.balance, 1 wei); + assertEq(_recipient2.balance, 1 wei); + + _collectAndPrintLogs(false); + } + + function testExecuteBatchDecodeEmpty() public { + bytes memory fakeCalls = abi.encodePacked( + uint256(1), // Length of execution[] + uint256(0x20), // offset + uint256(uint160(_recipient1)), // target + uint256(1), // value: 1 wei + uint256(0x60), // offset of data + uint256(0) // length of + ); + + PackedUserOperation[] memory userOps = new PackedUserOperation[](1); + userOps[0] = PackedUserOperation({ + sender: _account, + nonce: 0, + initCode: "", + callData: abi.encodeCall( + SampleAccount.execute, + ( + ERC7579Utils.encodeMode( + ERC7579Utils.CALLTYPE_BATCH, + ERC7579Utils.EXECTYPE_DEFAULT, + ModeSelector.wrap(0x00), + ModePayload.wrap(0x00) + ), + abi.encodePacked( + uint256(0x70) // fake offset pointing to paymasterAndData + ) + ) + ), + accountGasLimits: _packGas(500_000, 500_000), + preVerificationGas: 0, + gasFees: _packGas(1, 1), + paymasterAndData: abi.encodePacked(address(0), fakeCalls), + signature: "" + }); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign( + _ownerKey, + this.hashUserOperation(userOps[0]).toEthSignedMessageHash() + ); + userOps[0].signature = abi.encodePacked(r, s, v); + + vm.expectRevert( + abi.encodeWithSelector( + IEntryPoint.FailedOpWithRevert.selector, + 0, + "AA23 reverted", + abi.encodeWithSelector(ERC7579Utils.ERC7579DecodingError.selector) + ) + ); + ENTRYPOINT.handleOps(userOps, payable(_beneficiary)); + + _collectAndPrintLogs(false); + } + + function testExecuteBatchDecodeDifferent() public { + bytes memory execCallData = abi.encodePacked( + uint256(0x20), // offset pointing to the next segment + uint256(5), // Length of execution[] + uint256(0), // offset of calls[0], and target (!!) + uint256(0x20), // offset of calls[1], and value (!!) + uint256(0), // offset of calls[2], and rel offset of data (!!) + uint256(0) // offset of calls[3]. + // There is one more to read by the array length, but it's not present here. This will be + // paymasterAndData.length during validation, pointing to an all-zero call. + // During execution, the offset will be 0, pointing to a call with value. + ); + + PackedUserOperation[] memory userOps = new PackedUserOperation[](1); + userOps[0] = PackedUserOperation({ + sender: _account, + nonce: 0, + initCode: "", + callData: abi.encodePacked( + SampleAccount.execute.selector, + ERC7579Utils.encodeMode( + ERC7579Utils.CALLTYPE_BATCH, + ERC7579Utils.EXECTYPE_DEFAULT, + ModeSelector.wrap(0x00), + ModePayload.wrap(0x00) + ), + uint256(0x5c), // offset pointing to the next segment + uint224(type(uint224).max), // Padding to align the `bytes` types + // type(uint256).max, // unknown padding + uint256(execCallData.length), // Length of the data + execCallData + ), + accountGasLimits: _packGas(500_000, 500_000), + preVerificationGas: 0, + gasFees: _packGas(1, 1), + paymasterAndData: abi.encodePacked(uint256(0), uint256(0)), // padding length to create an offset + signature: "" + }); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign( + _ownerKey, + this.hashUserOperation(userOps[0]).toEthSignedMessageHash() + ); + userOps[0].signature = abi.encodePacked(r, s, v); + + vm.expectRevert( + abi.encodeWithSelector( + IEntryPoint.FailedOpWithRevert.selector, + 0, + "AA23 reverted", + abi.encodeWithSelector(ERC7579Utils.ERC7579DecodingError.selector) + ) + ); + ENTRYPOINT.handleOps(userOps, payable(_beneficiary)); + + _collectAndPrintLogs(true); + } + + function testDecodeBatch() public { + // BAD: buffer empty + vm.expectRevert(ERC7579Utils.ERC7579DecodingError.selector); + this.callDecodeBatch(""); + + // BAD: buffer too short + vm.expectRevert(ERC7579Utils.ERC7579DecodingError.selector); + this.callDecodeBatch(abi.encodePacked(uint128(0))); + + // GOOD + this.callDecodeBatch(abi.encode(0)); + // Note: Solidity also supports this even though it's odd. Offset 0 means array is at the same location, which + // is interpreted as an array of length 0, which doesn't require any more data + // solhint-disable-next-line var-name-mixedcase + uint256[] memory _1 = abi.decode(abi.encode(0), (uint256[])); + _1; + + // BAD: offset is out of bounds + vm.expectRevert(ERC7579Utils.ERC7579DecodingError.selector); + this.callDecodeBatch(abi.encode(1)); + + // GOOD + this.callDecodeBatch(abi.encode(32, 0)); + + // BAD: reported array length extends beyond bounds + vm.expectRevert(ERC7579Utils.ERC7579DecodingError.selector); + this.callDecodeBatch(abi.encode(32, 1)); + + // GOOD + this.callDecodeBatch(abi.encode(32, 1, 0)); + + // GOOD + // + // 0000000000000000000000000000000000000000000000000000000000000020 (32) offset + // 0000000000000000000000000000000000000000000000000000000000000001 ( 1) array length + // 0000000000000000000000000000000000000000000000000000000000000020 (32) element 0 offset + // 000000000000000000000000xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx (recipient) target for element #0 + // 000000000000000000000000000000000000000000000000000000000000002a (42) value for element #0 + // 0000000000000000000000000000000000000000000000000000000000000060 (96) offset to calldata for element #0 + // 000000000000000000000000000000000000000000000000000000000000000c (12) length of the calldata for element #0 + // 48656c6c6f20576f726c64210000000000000000000000000000000000000000 (..) buffer for the calldata for element #0 + assertEq( + bytes("Hello World!"), + this.callDecodeBatchAndGetFirstBytes( + abi.encode(32, 1, 32, _recipient1, 42, 96, 12, bytes12("Hello World!")) + ) + ); + + // This is invalid, the first element of the array points is out of bounds + // but we allow it past initial validation, because solidity will validate later when the bytes field is accessed + // + // 0000000000000000000000000000000000000000000000000000000000000020 (32) offset + // 0000000000000000000000000000000000000000000000000000000000000001 ( 1) array length + // 0000000000000000000000000000000000000000000000000000000000000020 (32) element 0 offset + // + bytes memory invalid = abi.encode(32, 1, 32); + this.callDecodeBatch(invalid); + vm.expectRevert(); + this.callDecodeBatchAndGetFirst(invalid); + + // this is invalid: the bytes field of the first element of the array is out of bounds + // but we allow it past initial validation, because solidity will validate later when the bytes field is accessed + // + // 0000000000000000000000000000000000000000000000000000000000000020 (32) offset + // 0000000000000000000000000000000000000000000000000000000000000001 ( 1) array length + // 0000000000000000000000000000000000000000000000000000000000000020 (32) element 0 offset + // 000000000000000000000000xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx (recipient) target for element #0 + // 000000000000000000000000000000000000000000000000000000000000002a (42) value for element #0 + // 0000000000000000000000000000000000000000000000000000000000000060 (96) offset to calldata for element #0 + // + bytes memory invalidDeeply = abi.encode(32, 1, 32, _recipient1, 42, 96); + this.callDecodeBatch(invalidDeeply); + // Note that this is ok because we don't return the value. Returning it would introduce a check that would fails. + this.callDecodeBatchAndGetFirst(invalidDeeply); + vm.expectRevert(); + this.callDecodeBatchAndGetFirstBytes(invalidDeeply); + } + + function callDecodeBatch(bytes calldata executionCalldata) public pure { + ERC7579Utils.decodeBatch(executionCalldata); + } + + function callDecodeBatchAndGetFirst(bytes calldata executionCalldata) public pure { + ERC7579Utils.decodeBatch(executionCalldata)[0]; + } + + function callDecodeBatchAndGetFirstBytes(bytes calldata executionCalldata) public pure returns (bytes calldata) { + return ERC7579Utils.decodeBatch(executionCalldata)[0].callData; + } + + function hashUserOperation(PackedUserOperation calldata useroperation) public view returns (bytes32) { + return useroperation.hash(address(ENTRYPOINT), block.chainid); + } + + function _collectAndPrintLogs(bool includeTotalValue) internal { + Vm.Log[] memory logs = vm.getRecordedLogs(); + for (uint256 i = 0; i < logs.length; i++) { + if (logs[i].emitter == _account) { + _printDecodedCalls(logs[i].data, includeTotalValue); + } + } + } + + function _printDecodedCalls(bytes memory logData, bool includeTotalValue) internal pure { + (bool duringValidation, Execution[] memory calls) = abi.decode(logData, (bool, Execution[])); + + console.log( + string.concat( + "Batch execute contents, as read during ", + duringValidation ? "validation" : "execution", + ": " + ) + ); + console.log(" Execution[] length: %s", calls.length); + + uint256 totalValue = 0; + for (uint256 i = 0; i < calls.length; ++i) { + console.log(string.concat(" calls[", vm.toString(i), "].target = ", vm.toString(calls[i].target))); + console.log(string.concat(" calls[", vm.toString(i), "].value = ", vm.toString(calls[i].value))); + console.log(string.concat(" calls[", vm.toString(i), "].data = ", vm.toString(calls[i].callData))); + totalValue += calls[i].value; + } + + if (includeTotalValue) { + console.log(" Total value: %s", totalValue); + } + } + + function _packGas(uint256 upper, uint256 lower) internal pure returns (bytes32) { + return bytes32(uint256((upper << 128) | uint128(lower))); + } +} diff --git a/test/account/utils/draft-ERC7579Utils.test.js b/test/account/utils/draft-ERC7579Utils.test.js index e72b6698d60..7419c667b69 100644 --- a/test/account/utils/draft-ERC7579Utils.test.js +++ b/test/account/utils/draft-ERC7579Utils.test.js @@ -34,7 +34,7 @@ describe('ERC7579Utils', function () { const value = 0x012; const data = encodeSingle(this.target, value, this.target.interface.encodeFunctionData('mockFunction')); - await expect(this.utils.$execSingle(EXEC_TYPE_DEFAULT, data)).to.emit(this.target, 'MockFunctionCalled'); + await expect(this.utils.$execSingle(data, EXEC_TYPE_DEFAULT)).to.emit(this.target, 'MockFunctionCalled'); expect(ethers.provider.getBalance(this.target)).to.eventually.equal(value); }); @@ -47,7 +47,7 @@ describe('ERC7579Utils', function () { this.target.interface.encodeFunctionData('mockFunctionWithArgs', [42, '0x1234']), ); - await expect(this.utils.$execSingle(EXEC_TYPE_DEFAULT, data)) + await expect(this.utils.$execSingle(data, EXEC_TYPE_DEFAULT)) .to.emit(this.target, 'MockFunctionCalledWithArgs') .withArgs(42, '0x1234'); @@ -62,7 +62,7 @@ describe('ERC7579Utils', function () { this.target.interface.encodeFunctionData('mockFunctionRevertsReason'), ); - await expect(this.utils.$execSingle(EXEC_TYPE_DEFAULT, data)).to.be.revertedWith('CallReceiverMock: reverting'); + await expect(this.utils.$execSingle(data, EXEC_TYPE_DEFAULT)).to.be.revertedWith('CallReceiverMock: reverting'); }); it('emits ERC7579TryExecuteFail event when target reverts in try ExecType', async function () { @@ -73,7 +73,7 @@ describe('ERC7579Utils', function () { this.target.interface.encodeFunctionData('mockFunctionRevertsReason'), ); - await expect(this.utils.$execSingle(EXEC_TYPE_TRY, data)) + await expect(this.utils.$execSingle(data, EXEC_TYPE_TRY)) .to.emit(this.utils, 'ERC7579TryExecuteFail') .withArgs( CALL_TYPE_CALL, @@ -88,7 +88,7 @@ describe('ERC7579Utils', function () { const value = 0x012; const data = encodeSingle(this.target, value, this.target.interface.encodeFunctionData('mockFunction')); - await expect(this.utils.$execSingle('0x03', data)) + await expect(this.utils.$execSingle(data, '0x03')) .to.be.revertedWithCustomError(this.utils, 'ERC7579UnsupportedExecType') .withArgs('0x03'); }); @@ -103,7 +103,7 @@ describe('ERC7579Utils', function () { [this.anotherTarget, value2, this.anotherTarget.interface.encodeFunctionData('mockFunction')], ); - await expect(this.utils.$execBatch(EXEC_TYPE_DEFAULT, data)) + await expect(this.utils.$execBatch(data, EXEC_TYPE_DEFAULT)) .to.emit(this.target, 'MockFunctionCalled') .to.emit(this.anotherTarget, 'MockFunctionCalled'); @@ -123,7 +123,7 @@ describe('ERC7579Utils', function () { ], ); - await expect(this.utils.$execBatch(EXEC_TYPE_DEFAULT, data)) + await expect(this.utils.$execBatch(data, EXEC_TYPE_DEFAULT)) .to.emit(this.target, 'MockFunctionCalledWithArgs') .to.emit(this.anotherTarget, 'MockFunctionCalledWithArgs'); @@ -139,7 +139,7 @@ describe('ERC7579Utils', function () { [this.anotherTarget, value2, this.anotherTarget.interface.encodeFunctionData('mockFunctionRevertsReason')], ); - await expect(this.utils.$execBatch(EXEC_TYPE_DEFAULT, data)).to.be.revertedWith('CallReceiverMock: reverting'); + await expect(this.utils.$execBatch(data, EXEC_TYPE_DEFAULT)).to.be.revertedWith('CallReceiverMock: reverting'); }); it('emits ERC7579TryExecuteFail event when any target reverts in try ExecType', async function () { @@ -150,7 +150,7 @@ describe('ERC7579Utils', function () { [this.anotherTarget, value2, this.anotherTarget.interface.encodeFunctionData('mockFunctionRevertsReason')], ); - await expect(this.utils.$execBatch(EXEC_TYPE_TRY, data)) + await expect(this.utils.$execBatch(data, EXEC_TYPE_TRY)) .to.emit(this.utils, 'ERC7579TryExecuteFail') .withArgs( CALL_TYPE_BATCH, @@ -173,7 +173,7 @@ describe('ERC7579Utils', function () { [this.anotherTarget, value2, this.anotherTarget.interface.encodeFunctionData('mockFunction')], ); - await expect(this.utils.$execBatch('0x03', data)) + await expect(this.utils.$execBatch(data, '0x03')) .to.be.revertedWithCustomError(this.utils, 'ERC7579UnsupportedExecType') .withArgs('0x03'); }); @@ -189,20 +189,20 @@ describe('ERC7579Utils', function () { ); expect(ethers.provider.getStorage(this.utils.target, slot)).to.eventually.equal(ethers.ZeroHash); - await this.utils.$execDelegateCall(EXEC_TYPE_DEFAULT, data); + await this.utils.$execDelegateCall(data, EXEC_TYPE_DEFAULT); expect(ethers.provider.getStorage(this.utils.target, slot)).to.eventually.equal(value); }); it('reverts when target reverts in default ExecType', async function () { const data = encodeDelegate(this.target, this.target.interface.encodeFunctionData('mockFunctionRevertsReason')); - await expect(this.utils.$execDelegateCall(EXEC_TYPE_DEFAULT, data)).to.be.revertedWith( + await expect(this.utils.$execDelegateCall(data, EXEC_TYPE_DEFAULT)).to.be.revertedWith( 'CallReceiverMock: reverting', ); }); it('emits ERC7579TryExecuteFail event when target reverts in try ExecType', async function () { const data = encodeDelegate(this.target, this.target.interface.encodeFunctionData('mockFunctionRevertsReason')); - await expect(this.utils.$execDelegateCall(EXEC_TYPE_TRY, data)) + await expect(this.utils.$execDelegateCall(data, EXEC_TYPE_TRY)) .to.emit(this.utils, 'ERC7579TryExecuteFail') .withArgs( CALL_TYPE_CALL, @@ -215,7 +215,7 @@ describe('ERC7579Utils', function () { it('reverts with an invalid exec type', async function () { const data = encodeDelegate(this.target, this.target.interface.encodeFunctionData('mockFunction')); - await expect(this.utils.$execDelegateCall('0x03', data)) + await expect(this.utils.$execDelegateCall(data, '0x03')) .to.be.revertedWithCustomError(this.utils, 'ERC7579UnsupportedExecType') .withArgs('0x03'); }); From a71f79fbbcfe2ccdaf741cc33d781607c6769314 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Fri, 13 Dec 2024 17:54:45 +0100 Subject: [PATCH 82/84] Remove relative path in solhint.config.js in favor of npm virtual package (#5368) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernesto García --- package-lock.json | 5 ++++- scripts/solhint-custom/package.json | 5 ++++- solhint.config.js | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 79968635cd5..900b5884548 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12203,7 +12203,10 @@ "scripts/solhint-custom": { "name": "solhint-plugin-openzeppelin", "version": "0.0.0", - "dev": true + "dev": true, + "dependencies": { + "minimatch": "^3.1.2" + } } } } diff --git a/scripts/solhint-custom/package.json b/scripts/solhint-custom/package.json index 075eb929dea..ce9690d74ef 100644 --- a/scripts/solhint-custom/package.json +++ b/scripts/solhint-custom/package.json @@ -1,5 +1,8 @@ { "name": "solhint-plugin-openzeppelin", "version": "0.0.0", - "private": true + "private": true, + "dependencies": { + "minimatch": "^3.1.2" + } } diff --git a/solhint.config.js b/solhint.config.js index f0bd7994f97..47c1cebfe25 100644 --- a/solhint.config.js +++ b/solhint.config.js @@ -1,4 +1,4 @@ -const customRules = require('./scripts/solhint-custom'); +const customRules = require('solhint-plugin-openzeppelin'); const rules = [ 'avoid-tx-origin', From 535b54da5967f0fcfe06cf4eb9b1805c5d3e8681 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Fri, 13 Dec 2024 12:46:04 -0600 Subject: [PATCH 83/84] Rename `arrayLengthPointer` to `arrayLengthOffset` and add changeset (#5371) --- .changeset/seven-insects-taste.md | 5 +++++ contracts/account/utils/draft-ERC7579Utils.sol | 16 ++++++++-------- 2 files changed, 13 insertions(+), 8 deletions(-) create mode 100644 .changeset/seven-insects-taste.md diff --git a/.changeset/seven-insects-taste.md b/.changeset/seven-insects-taste.md new file mode 100644 index 00000000000..bfa8737d7de --- /dev/null +++ b/.changeset/seven-insects-taste.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': patch +--- + +`ERC7579Utils`: Add ABI decoding checks on calldata bounds within `decodeBatch` diff --git a/contracts/account/utils/draft-ERC7579Utils.sol b/contracts/account/utils/draft-ERC7579Utils.sol index c672fb279fe..8be094e649d 100644 --- a/contracts/account/utils/draft-ERC7579Utils.sol +++ b/contracts/account/utils/draft-ERC7579Utils.sol @@ -182,14 +182,14 @@ library ERC7579Utils { if (bufferLength < 32) revert ERC7579DecodingError(); // Get the offset of the array (pointer to the array length). - uint256 arrayLengthPointer = uint256(bytes32(executionCalldata[0:32])); + uint256 arrayLengthOffset = uint256(bytes32(executionCalldata[0:32])); - // The array length (at arrayLengthPointer) should be 32 bytes long. We check that this is within the + // The array length (at arrayLengthOffset) should be 32 bytes long. We check that this is within the // buffer bounds. Since we know bufferLength is at least 32, we can subtract with no overflow risk. - if (arrayLengthPointer > bufferLength - 32) revert ERC7579DecodingError(); + if (arrayLengthOffset > bufferLength - 32) revert ERC7579DecodingError(); - // Get the array length. arrayLengthPointer + 32 is bounded by bufferLength so it does not overflow. - uint256 arrayLength = uint256(bytes32(executionCalldata[arrayLengthPointer:arrayLengthPointer + 32])); + // Get the array length. arrayLengthOffset + 32 is bounded by bufferLength so it does not overflow. + uint256 arrayLength = uint256(bytes32(executionCalldata[arrayLengthOffset:arrayLengthOffset + 32])); // Check that the buffer is long enough to store the array elements as "offset pointer": // - each element of the array is an "offset pointer" to the data. @@ -197,13 +197,13 @@ library ERC7579Utils { // - validity of the calldata at that location is checked when the array element is accessed, so we only // need to check that the buffer is large enough to hold the pointers. // - // Since we know bufferLength is at least arrayLengthPointer + 32, we can subtract with no overflow risk. + // Since we know bufferLength is at least arrayLengthOffset + 32, we can subtract with no overflow risk. // Solidity limits length of such arrays to 2**64-1, this guarantees `arrayLength * 32` does not overflow. - if (arrayLength > type(uint64).max || bufferLength - arrayLengthPointer - 32 < arrayLength * 32) + if (arrayLength > type(uint64).max || bufferLength - arrayLengthOffset - 32 < arrayLength * 32) revert ERC7579DecodingError(); assembly ("memory-safe") { - executionBatch.offset := add(add(executionCalldata.offset, arrayLengthPointer), 32) + executionBatch.offset := add(add(executionCalldata.offset, arrayLengthOffset), 32) executionBatch.length := arrayLength } } From f20c062ead2addc99c570956d18fbb0d2a23dc2c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 16 Dec 2024 09:38:00 +0100 Subject: [PATCH 84/84] Update dependency undici to v7 (#5327) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package-lock.json | 11 ++++++----- package.json | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 900b5884548..2b7f5083416 100644 --- a/package-lock.json +++ b/package-lock.json @@ -46,7 +46,7 @@ "solidity-ast": "^0.4.50", "solidity-coverage": "^0.8.5", "solidity-docgen": "^0.6.0-beta.29", - "undici": "^6.11.1", + "undici": "^7.0.0", "yargs": "^17.0.0" } }, @@ -11494,12 +11494,13 @@ } }, "node_modules/undici": { - "version": "6.12.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.12.0.tgz", - "integrity": "sha512-d87yk8lqSFUYtR5fTFe2frpkMIrUEz+lgoJmhcL+J3StVl+8fj8ytE4lLnJOTPCE12YbumNGzf4LYsQyusdV5g==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.0.0.tgz", + "integrity": "sha512-c4xi3kWnQJrb7h2q8aJYKvUzmz7boCgz1cUCC6OwdeM5Tr2P0hDuthr2iut4ggqsz+Cnh20U/LoTzbKIdDS/Nw==", "dev": true, + "license": "MIT", "engines": { - "node": ">=18.0" + "node": ">=20.18.1" } }, "node_modules/undici-types": { diff --git a/package.json b/package.json index db00d612deb..d616c5583cd 100644 --- a/package.json +++ b/package.json @@ -88,7 +88,7 @@ "solidity-ast": "^0.4.50", "solidity-coverage": "^0.8.5", "solidity-docgen": "^0.6.0-beta.29", - "undici": "^6.11.1", + "undici": "^7.0.0", "yargs": "^17.0.0" }, "lint-staged": {