Skip to content

Commit 036c3cb

Browse files
Amxxernestognw
andauthored
Replace custom errors with native panic codes in DoubleEndedQueue (#4872)
Co-authored-by: ernestognw <[email protected]>
1 parent e73913c commit 036c3cb

File tree

9 files changed

+71
-39
lines changed

9 files changed

+71
-39
lines changed

.changeset/gentle-bulldogs-turn.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'openzeppelin-solidity': minor
3+
---
4+
5+
`DoubleEndedQueue`: Custom errors replaced with native panic codes.

.changeset/smart-bugs-switch.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
'openzeppelin-solidity': minor
33
---
44

5-
`Math`: MathOverflowedMulDiv error was replaced with native panic codes.
5+
`Math`: Custom errors replaced with native panic codes.

contracts/utils/Panic.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ pragma solidity ^0.8.20;
1717
* }
1818
* ```
1919
*
20-
* Follows the list from libsolutil: https://github.com/ethereum/solidity/blob/v0.8.24/libsolutil/ErrorCodes.h
20+
* Follows the list from https://github.com/ethereum/solidity/blob/v0.8.24/libsolutil/ErrorCodes.h[libsolutil].
2121
*/
2222
// slither-disable-next-line unused-state
2323
library Panic {

contracts/utils/README.adoc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ Miscellaneous contracts and libraries containing utility functions you can use t
2929
* {StorageSlot}: Methods for accessing specific storage slots formatted as common primitive types.
3030
* {Multicall}: Abstract contract with an utility to allow batching together multiple calls in a single transaction. Useful for allowing EOAs to perform multiple operations at once.
3131
* {Context}: An utility for abstracting the sender and calldata in the current execution context.
32+
* {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].
3233

3334
[NOTE]
3435
====
@@ -106,3 +107,5 @@ Ethereum contracts have no native concept of an interface, so applications must
106107
{{Multicall}}
107108

108109
{{Context}}
110+
111+
{{Panic}}

contracts/utils/structs/DoubleEndedQueue.sol

Lines changed: 16 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// OpenZeppelin Contracts (last updated v5.0.0) (utils/structs/DoubleEndedQueue.sol)
33
pragma solidity ^0.8.20;
44

5+
import {Panic} from "../Panic.sol";
6+
57
/**
68
* @dev A sequence of items with the ability to efficiently push and pop items (i.e. insert and remove) on both ends of
79
* the sequence (called front and back). Among other access patterns, it can be used to implement efficient LIFO and
@@ -15,21 +17,6 @@ pragma solidity ^0.8.20;
1517
* ```
1618
*/
1719
library DoubleEndedQueue {
18-
/**
19-
* @dev An operation (e.g. {front}) couldn't be completed due to the queue being empty.
20-
*/
21-
error QueueEmpty();
22-
23-
/**
24-
* @dev A push operation couldn't be completed due to the queue being full.
25-
*/
26-
error QueueFull();
27-
28-
/**
29-
* @dev An operation (e.g. {at}) couldn't be completed due to an index being out of bounds.
30-
*/
31-
error QueueOutOfBounds();
32-
3320
/**
3421
* @dev Indices are 128 bits so begin and end are packed in a single storage slot for efficient access.
3522
*
@@ -48,12 +35,12 @@ library DoubleEndedQueue {
4835
/**
4936
* @dev Inserts an item at the end of the queue.
5037
*
51-
* Reverts with {QueueFull} if the queue is full.
38+
* Reverts with {Panic-RESOURCE_ERROR} if the queue is full.
5239
*/
5340
function pushBack(Bytes32Deque storage deque, bytes32 value) internal {
5441
unchecked {
5542
uint128 backIndex = deque._end;
56-
if (backIndex + 1 == deque._begin) revert QueueFull();
43+
if (backIndex + 1 == deque._begin) Panic.panic(Panic.RESOURCE_ERROR);
5744
deque._data[backIndex] = value;
5845
deque._end = backIndex + 1;
5946
}
@@ -62,12 +49,12 @@ library DoubleEndedQueue {
6249
/**
6350
* @dev Removes the item at the end of the queue and returns it.
6451
*
65-
* Reverts with {QueueEmpty} if the queue is empty.
52+
* Reverts with {Panic-EMPTY_ARRAY_POP} if the queue is empty.
6653
*/
6754
function popBack(Bytes32Deque storage deque) internal returns (bytes32 value) {
6855
unchecked {
6956
uint128 backIndex = deque._end;
70-
if (backIndex == deque._begin) revert QueueEmpty();
57+
if (backIndex == deque._begin) Panic.panic(Panic.EMPTY_ARRAY_POP);
7158
--backIndex;
7259
value = deque._data[backIndex];
7360
delete deque._data[backIndex];
@@ -78,12 +65,12 @@ library DoubleEndedQueue {
7865
/**
7966
* @dev Inserts an item at the beginning of the queue.
8067
*
81-
* Reverts with {QueueFull} if the queue is full.
68+
* Reverts with {Panic-RESOURCE_ERROR} if the queue is full.
8269
*/
8370
function pushFront(Bytes32Deque storage deque, bytes32 value) internal {
8471
unchecked {
8572
uint128 frontIndex = deque._begin - 1;
86-
if (frontIndex == deque._end) revert QueueFull();
73+
if (frontIndex == deque._end) Panic.panic(Panic.RESOURCE_ERROR);
8774
deque._data[frontIndex] = value;
8875
deque._begin = frontIndex;
8976
}
@@ -92,12 +79,12 @@ library DoubleEndedQueue {
9279
/**
9380
* @dev Removes the item at the beginning of the queue and returns it.
9481
*
95-
* Reverts with `QueueEmpty` if the queue is empty.
82+
* Reverts with {Panic-EMPTY_ARRAY_POP} if the queue is empty.
9683
*/
9784
function popFront(Bytes32Deque storage deque) internal returns (bytes32 value) {
9885
unchecked {
9986
uint128 frontIndex = deque._begin;
100-
if (frontIndex == deque._end) revert QueueEmpty();
87+
if (frontIndex == deque._end) Panic.panic(Panic.EMPTY_ARRAY_POP);
10188
value = deque._data[frontIndex];
10289
delete deque._data[frontIndex];
10390
deque._begin = frontIndex + 1;
@@ -107,20 +94,20 @@ library DoubleEndedQueue {
10794
/**
10895
* @dev Returns the item at the beginning of the queue.
10996
*
110-
* Reverts with `QueueEmpty` if the queue is empty.
97+
* Reverts with {Panic-ARRAY_OUT_OF_BOUNDS} if the queue is empty.
11198
*/
11299
function front(Bytes32Deque storage deque) internal view returns (bytes32 value) {
113-
if (empty(deque)) revert QueueEmpty();
100+
if (empty(deque)) Panic.panic(Panic.ARRAY_OUT_OF_BOUNDS);
114101
return deque._data[deque._begin];
115102
}
116103

117104
/**
118105
* @dev Returns the item at the end of the queue.
119106
*
120-
* Reverts with `QueueEmpty` if the queue is empty.
107+
* Reverts with {Panic-ARRAY_OUT_OF_BOUNDS} if the queue is empty.
121108
*/
122109
function back(Bytes32Deque storage deque) internal view returns (bytes32 value) {
123-
if (empty(deque)) revert QueueEmpty();
110+
if (empty(deque)) Panic.panic(Panic.ARRAY_OUT_OF_BOUNDS);
124111
unchecked {
125112
return deque._data[deque._end - 1];
126113
}
@@ -130,10 +117,10 @@ library DoubleEndedQueue {
130117
* @dev Return the item at a position in the queue given by `index`, with the first item at 0 and last item at
131118
* `length(deque) - 1`.
132119
*
133-
* Reverts with `QueueOutOfBounds` if the index is out of bounds.
120+
* Reverts with {Panic-ARRAY_OUT_OF_BOUNDS} if the index is out of bounds.
134121
*/
135122
function at(Bytes32Deque storage deque, uint256 index) internal view returns (bytes32 value) {
136-
if (index >= length(deque)) revert QueueOutOfBounds();
123+
if (index >= length(deque)) Panic.panic(Panic.ARRAY_OUT_OF_BOUNDS);
137124
// By construction, length is a uint128, so the check above ensures that index can be safely downcast to uint128
138125
unchecked {
139126
return deque._data[deque._begin + uint128(index)];

docs/templates/contract.hbs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,23 @@ import "@openzeppelin/{{__item_context.file.absolutePath}}";
7474
--
7575
{{/if}}
7676

77+
{{#if has-internal-variables}}
78+
[.contract-index]
79+
.Internal Variables
80+
--
81+
{{#each inheritance}}
82+
{{#unless @first}}
83+
[.contract-subindex-inherited]
84+
.{{name}}
85+
{{/unless}}
86+
{{#each internal-variables}}
87+
* {xref-{{anchor~}} }[`++{{typeDescriptions.typeString}} {{#if constant}}constant{{/if}} {{name}}++`]
88+
{{/each}}
89+
90+
{{/each}}
91+
--
92+
{{/if}}
93+
7794
{{#each modifiers}}
7895
[.contract-item]
7996
[[{{anchor}}]]
@@ -109,3 +126,12 @@ import "@openzeppelin/{{__item_context.file.absolutePath}}";
109126
{{{natspec.dev}}}
110127

111128
{{/each}}
129+
130+
{{#each internal-variables}}
131+
[.contract-item]
132+
[[{{anchor}}]]
133+
==== `{{typeDescriptions.typeString}} [.contract-item-name]#++{{name}}++#` [.item-kind]#internal{{#if constant}} constant{{/if}}#
134+
135+
{{{natspec.dev}}}
136+
137+
{{/each}}

docs/templates/properties.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,14 @@ module.exports['has-errors'] = function ({ item }) {
3939
return item.inheritance.some(c => c.errors.length > 0);
4040
};
4141

42+
module.exports['internal-variables'] = function ({ item }) {
43+
return item.variables.filter(({ visibility }) => visibility === 'internal');
44+
};
45+
46+
module.exports['has-internal-variables'] = function ({ item }) {
47+
return module.exports['internal-variables']({ item }).length > 0;
48+
};
49+
4250
module.exports.functions = function ({ item }) {
4351
return [
4452
...[...findAll('FunctionDefinition', item)].filter(f => f.visibility !== 'private'),

test/governance/extensions/GovernorTimelockControl.test.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ const { ethers } = require('hardhat');
22
const { expect } = require('chai');
33
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
44
const { anyValue } = require('@nomicfoundation/hardhat-chai-matchers/withArgs');
5+
const { PANIC_CODES } = require('@nomicfoundation/hardhat-chai-matchers/panic');
56

67
const { GovernorHelper, timelockSalt } = require('../../helpers/governance');
78
const { OperationState, ProposalState, VoteType } = require('../../helpers/enums');
@@ -377,9 +378,8 @@ describe('GovernorTimelockControl', function () {
377378
await time.increaseBy.timestamp(delay);
378379

379380
// Error bubbled up from Governor
380-
await expect(this.timelock.connect(this.owner).execute(...call)).to.be.revertedWithCustomError(
381-
this.mock,
382-
'QueueEmpty',
381+
await expect(this.timelock.connect(this.owner).execute(...call)).to.be.revertedWithPanic(
382+
PANIC_CODES.POP_ON_EMPTY_ARRAY,
383383
);
384384
});
385385
});

test/utils/structs/DoubleEndedQueue.test.js

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
const { ethers } = require('hardhat');
22
const { expect } = require('chai');
33
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
4+
const { PANIC_CODES } = require('@nomicfoundation/hardhat-chai-matchers/panic');
45

56
async function fixture() {
67
const mock = await ethers.deployContract('$DoubleEndedQueue');
@@ -36,10 +37,10 @@ describe('DoubleEndedQueue', function () {
3637
});
3738

3839
it('reverts on accesses', async function () {
39-
await expect(this.mock.$popBack(0)).to.be.revertedWithCustomError(this.mock, 'QueueEmpty');
40-
await expect(this.mock.$popFront(0)).to.be.revertedWithCustomError(this.mock, 'QueueEmpty');
41-
await expect(this.mock.$back(0)).to.be.revertedWithCustomError(this.mock, 'QueueEmpty');
42-
await expect(this.mock.$front(0)).to.be.revertedWithCustomError(this.mock, 'QueueEmpty');
40+
await expect(this.mock.$popBack(0)).to.be.revertedWithPanic(PANIC_CODES.POP_ON_EMPTY_ARRAY);
41+
await expect(this.mock.$popFront(0)).to.be.revertedWithPanic(PANIC_CODES.POP_ON_EMPTY_ARRAY);
42+
await expect(this.mock.$back(0)).to.be.revertedWithPanic(PANIC_CODES.ARRAY_ACCESS_OUT_OF_BOUNDS);
43+
await expect(this.mock.$front(0)).to.be.revertedWithPanic(PANIC_CODES.ARRAY_ACCESS_OUT_OF_BOUNDS);
4344
});
4445
});
4546

@@ -60,7 +61,9 @@ describe('DoubleEndedQueue', function () {
6061
});
6162

6263
it('out of bounds access', async function () {
63-
await expect(this.mock.$at(0, this.content.length)).to.be.revertedWithCustomError(this.mock, 'QueueOutOfBounds');
64+
await expect(this.mock.$at(0, this.content.length)).to.be.revertedWithPanic(
65+
PANIC_CODES.ARRAY_ACCESS_OUT_OF_BOUNDS,
66+
);
6467
});
6568

6669
describe('push', function () {

0 commit comments

Comments
 (0)